Merge branch 'temp8' into temp8
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
_Looking for Umbraco version 8? [Click here](https://github.com/umbraco/Umbraco-CMS/blob/temp8/docs/CONTRIBUTING.md) to go to the v8 branch_
|
||||
_Looking for Umbraco version 8? [Click here](https://github.com/umbraco/Umbraco-CMS/blob/temp8/.github/V8_GETTING_STARTED.md) to go to the v8 branch_
|
||||
# Contributing to Umbraco CMS
|
||||
|
||||
👍🎉 First off, thanks for taking the time to contribute! 🎉👍
|
||||
@@ -16,9 +16,9 @@ This document gives you a quick overview on how to get started, we will link to
|
||||
|
||||
## Guidelines for contributions we welcome
|
||||
|
||||
Not all changes are wanted so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valueable time.
|
||||
Not all changes are wanted, so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valueable time.
|
||||
|
||||
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md), make sure to talk to us before making large changes.
|
||||
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes.
|
||||
|
||||
Remember, if an issue is in the `Up for grabs` list or you've asked for some feedback before you sent us a PR, your PR will not be closed as unwanted.
|
||||
|
||||
@@ -36,7 +36,7 @@ Great question! The short version goes like this:
|
||||
|
||||
* **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md)
|
||||
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
|
||||
* **Commit** - done? Yay! 🎉 It is recommended to create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-U4-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `U4-12345`
|
||||
* **Commit** - done? Yay! 🎉 It is recommended to create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`
|
||||
* **Push** - great, now you can push the changes up to your fork on GitHub
|
||||
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
|
||||
|
||||
@@ -72,7 +72,6 @@ The pull request team consists of a member of Umbraco HQ, [Sebastiaan](https://g
|
||||
- [Anders Bjerner](https://github.com/abjerner)
|
||||
- [Dave Woestenborghs](https://github.com/dawoe)
|
||||
- [Emma Burstow](https://github.com/emmaburstow)
|
||||
- [Kyle Weems](https://github.com/cssquirrel)
|
||||
- [Poornima Nayar](https://github.com/poornimanayar)
|
||||
|
||||
These wonderful volunteers will provide you with a first reply to your PR, review and test out your changes and might ask more questions. After that they'll let Umbraco HQ know if everything seems okay.
|
||||
|
||||
@@ -19,13 +19,13 @@ When contributing code to Umbraco there's plenty of things you'll want to know,
|
||||
* [What branch should I target for my contributions?](#what-branch-should-i-target-for-my-contributions)
|
||||
* [Building Umbraco from source code](#building-umbraco-from-source-code)
|
||||
* [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
|
||||
|
||||
|
||||
## How Can I Contribute?
|
||||
|
||||
### Reporting Bugs
|
||||
This section guides you through submitting a bug report for Umbraco CMS. Following these guidelines helps maintainers and the community understand your report 📝, reproduce the behavior 💻 💻, and find related reports 🔎.
|
||||
|
||||
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](http://issues.umbraco.org/issues#newissue=61-30118), the information it asks for helps us resolve issues faster.
|
||||
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](https://github.com/umbraco/Umbraco-CMS/issues/new/choose), the information it asks for helps us resolve issues faster.
|
||||
|
||||
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
|
||||
|
||||
@@ -52,7 +52,7 @@ Provide more context by answering these questions:
|
||||
|
||||
Include details about your configuration and environment:
|
||||
|
||||
* **Which version of Umbraco are you using?**
|
||||
* **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?**
|
||||
|
||||
@@ -80,7 +80,7 @@ The most successful pull requests usually look a like this:
|
||||
* Unit tests, while optional are awesome, thank you!
|
||||
* New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated
|
||||
|
||||
Again, these are guidelines, not strict requirements.
|
||||
Again, these are guidelines, not strict requirements.
|
||||
|
||||
## Making changes after the PR was opened
|
||||
|
||||
@@ -90,7 +90,7 @@ If you make the corrections we ask for in the same branch and push them to your
|
||||
|
||||
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.
|
||||
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?
|
||||
|
||||
@@ -125,6 +125,12 @@ We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-fl
|
||||
|
||||
### Building Umbraco from source code
|
||||
|
||||
In order to build the Umbraco source code locally, first make sure you have the following installed.
|
||||
|
||||
* Visual Studio 2017 v15.3+
|
||||
* Node v10+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v10)
|
||||
* npm v6.4.1+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v6.4.1)
|
||||
|
||||
The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
|
||||
|
||||
Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 (version 15.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
### Prerequisites
|
||||
|
||||
- [ ] I have [created an issue](https://github.com/umbraco/Umbraco-CMS/issues) for the proposed changes in this PR, the link is:
|
||||
- [ ] I have added steps to test this contribution in the description below
|
||||
|
||||
If there's an existing issue for this PR then this fixes: <!-- link to the issue here! -->
|
||||
|
||||
### Description
|
||||
<!-- A description of the changes proposed in the pull-request -->
|
||||
<!-- Make sure to link to the related issue number so we can easily find it in the issue tracker -->
|
||||
<!-- A description of the changes proposed in the pull-request and how to test these changes -->
|
||||
|
||||
|
||||
|
||||
<!-- Thanks for contributing to Umbraco CMS! -->
|
||||
|
||||
+10
-10
@@ -15,7 +15,7 @@ Once a track is done, we start releasing previews where we ask people to test th
|
||||
|
||||
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)
|
||||
The friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
|
||||
|
||||
[](https://vimeo.com/172382998/)
|
||||
|
||||
@@ -28,34 +28,34 @@ Umbraco is a free open source Content Management System built on the ASP.NET pla
|
||||
|
||||
## 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.
|
||||
For the first time on the Microsoft platform, there is a free user- and developer-friendly CMS that makes it quick and easy to create websites - and a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, right out of the box.
|
||||
|
||||
Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live.
|
||||
Umbraco is not only loved by developers, but is a content editor's dream. Enjoy intuitive editing tools, media management, responsive views, and approval workflows to send your content live.
|
||||
|
||||
Used by more than 443,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 220,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay.
|
||||
Used by more than 443,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scalable. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 220,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay.
|
||||
|
||||
To view more examples, please visit [https://umbraco.com/case-studies-testimonials/](https://umbraco.com/case-studies-testimonials/)
|
||||
|
||||
## Why Open Source?
|
||||
As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use.
|
||||
As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible, and community contributions and packages are available for all to use.
|
||||
|
||||
## Trying out Umbraco CMS
|
||||
|
||||
[Umbraco Cloud](https://umbraco.com/cloud) is the easiest and fastest way to use Umbraco yet with full support for all your custom .NET code and 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.
|
||||
[Umbraco Cloud](https://umbraco.com/cloud) is the easiest and fastest way to use Umbraco yet, with full support for all your custom .NET code and integrations. You're up and running in less than a minute, and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14-day trial, no credit card needed.
|
||||
|
||||
If you want to DIY you can [download Umbraco](https://our.umbraco.com/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host yourself and handling deployments and upgrades is all down to you.
|
||||
If you want to DIY, you can [download Umbraco](https://our.umbraco.com/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host it yourself, and handling deployments and upgrades will be all up to you.
|
||||
|
||||
## Community
|
||||
|
||||
Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.com). Our Umbraco feature forums for questions and answers, documentation, downloadable plugins for Umbraco and a rich collection of community resources.
|
||||
Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.com). Our Umbraco features forums for questions and answers, documentation, downloadable plugins for Umbraco, and a rich collection of community resources.
|
||||
|
||||
## Contribute to Umbraco
|
||||
|
||||
Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](CONTRIBUTING.md).
|
||||
Umbraco is contribution-focused and community-driven. If you want to contribute back to Umbraco, please check out our [guide to contributing](CONTRIBUTING.md).
|
||||
|
||||
## Found a bug?
|
||||
|
||||
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](CONTRIBUTING_DETAILED.md#reporting-bugs).
|
||||
|
||||
You can comment and report issues on the [github issue tracker](https://github.com/umbraco/Umbraco-CMS/issues).
|
||||
Since [September 2018](https://umbraco.com/blog/a-second-take-on-umbraco-issue-tracker-hello-github-issues/) the old issue tracker is in read only mode, but can still be found at [http://issues.umbraco.org](http://issues.umbraco.org).
|
||||
Since [September 2018](https://umbraco.com/blog/a-second-take-on-umbraco-issue-tracker-hello-github-issues/), the old issue tracker is in read-only mode, but can still be found at [http://issues.umbraco.org](http://issues.umbraco.org).
|
||||
|
||||
@@ -33,5 +33,5 @@ We recommend running the site with the Visual Studio since you'll be able to rem
|
||||
|
||||
We are keeping track of [known issues and limitations here](http://issues.umbraco.org/issue/U4-11279). These line items will eventually be turned into actual tasks to be worked on. Feel free to help us keep this list updated if you find issues and even help fix some of these items. If there is a particular item you'd like to help fix please mention this on the task and we'll create a sub task for the item to continue discussion there.
|
||||
|
||||
There's [a list of tasks for v8 that haven't been completed](https://issues.umbraco.org/issues?q=&project=U4&tagValue=&release=8.0.0&issueType=&resolvedState=open&search=search). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done.
|
||||
There's [a list of tasks for v8 that haven't been completed](https://github.com/umbraco/Umbraco-CMS/labels/release%2F8.0.0). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done.
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<dependency id="Serilog.Enrichers.Thread" version="[3.0.0,3.999999)" />
|
||||
<dependency id="Serilog.Filters.Expressions" version="[2.0.0,2.999999)" />
|
||||
<dependency id="Serilog.Formatting.Compact" version="[1.0.0,1.999999)" />
|
||||
<dependency id="Serilog.Settings.AppSettings" version="[2.1.2,2.999999)" />
|
||||
<dependency id="Serilog.Settings.AppSettings" version="[2.2.2,2.999999)" />
|
||||
<dependency id="Serilog.Sinks.File" version="[4.0.0,4.999999)" />
|
||||
<dependency id="Umbraco.SqlServerCE" version="[4.0.0.1,4.999999)" />
|
||||
<dependency id="NPoco" version="[3.9.4,3.999999)" />
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<dependency id="ClientDependency" version="[1.9.7,1.999999)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0,1.999999)" />
|
||||
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
|
||||
<dependency id="Examine" version="[1.0.0-beta025,1.999999)" />
|
||||
<dependency id="Examine" version="[1.0.0-beta046,1.999999)" />
|
||||
<dependency id="Lucene.Net.Contrib" version="[3.0.3,3.999999)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.8.9,1.999999)" />
|
||||
<dependency id="ImageProcessor" version="[2.6.2.25,2.999999)" />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section alias="StartupSettingsDashboardSection" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
|
||||
<tab caption="Get Started" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
|
||||
<tab caption="Welcome" xdt:Locator="Match(caption)" xdt:Transform="InsertIfMissing">
|
||||
<control showOnce="true" addPanel="true" panelCaption="" xdt:Transform="InsertIfMissing">
|
||||
<control panelCaption="" xdt:Transform="InsertIfMissing">
|
||||
views/dashboard/settings/settingsdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
@@ -14,7 +14,7 @@
|
||||
<area>forms</area>
|
||||
</areas>
|
||||
<tab caption="Install Umbraco Forms">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
<control panelCaption="">
|
||||
views/dashboard/forms/formsdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<section alias="StartupDeveloperDashboardSection">
|
||||
<tab caption="Get Started" xdt:Locator="Match(caption)" xdt:Transform="Replace">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
<control panelCaption="">
|
||||
views/dashboard/developer/developerdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
@@ -47,7 +47,7 @@
|
||||
<tab caption="Get Started" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
|
||||
<tab caption="Content" xdt:Locator="Match(caption)" xdt:Transform="InsertIfMissing" />
|
||||
<tab caption="Content" xdt:Locator="Match(caption)" xdt:Transform="Replace">
|
||||
<control showOnce="false" addPanel="false" panelCaption="">
|
||||
<control panelCaption="">
|
||||
views/dashboard/media/mediafolderbrowser.html
|
||||
</control>
|
||||
</tab>
|
||||
@@ -56,7 +56,7 @@
|
||||
<section alias="StartupMemberDashboardSection" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
|
||||
<tab caption="Get Started" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
|
||||
<tab caption="Get Started" xdt:Transform="Insert">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
<control panelCaption="">
|
||||
views/dashboard/members/membersdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
@@ -92,4 +92,4 @@
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
</dashBoard>
|
||||
</dashBoard>
|
||||
|
||||
+3
-11
@@ -43,14 +43,6 @@
|
||||
|
||||
$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"
|
||||
@@ -69,7 +61,7 @@
|
||||
$global:node_nodepath = $this.ClearEnvVar("NODEPATH")
|
||||
$global:node_npmcache = $this.ClearEnvVar("NPM_CONFIG_CACHE")
|
||||
$global:node_npmprefix = $this.ClearEnvVar("NPM_CONFIG_PREFIX")
|
||||
|
||||
|
||||
# https://github.com/gruntjs/grunt-contrib-connect/issues/235
|
||||
$this.SetEnvVar("NODE_NO_HTTP2", "1")
|
||||
})
|
||||
@@ -81,7 +73,7 @@
|
||||
$this.SetEnvVar("NODEPATH", $node_nodepath)
|
||||
$this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache)
|
||||
$this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix)
|
||||
|
||||
|
||||
$ignore = $this.ClearEnvVar("NODE_NO_HTTP2")
|
||||
})
|
||||
|
||||
@@ -434,7 +426,7 @@
|
||||
Write-Host "Prepare Azure Gallery"
|
||||
$this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput)
|
||||
})
|
||||
|
||||
|
||||
$ubuild.DefineMethod("Build",
|
||||
{
|
||||
$error.Clear()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<span class="pull-right">
|
||||
<a href="#top">Back to top</a>
|
||||
</span>
|
||||
<span>Copyright © 2016 Umbraco<br>Generated by <strong>DocFX</strong></span>
|
||||
<span>Copyright © 2016-present Umbraco<br>Generated by <strong>DocFX</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -70,10 +70,7 @@ namespace Umbraco.Core.Collections
|
||||
/// The number of elements contained in the <see cref="T:System.Collections.ICollection"/>.
|
||||
/// </returns>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public int Count
|
||||
{
|
||||
get { return GetThreadSafeClone().Count; }
|
||||
}
|
||||
public int Count => GetThreadSafeClone().Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
|
||||
@@ -81,10 +78,7 @@ namespace Umbraco.Core.Collections
|
||||
/// <returns>
|
||||
/// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
|
||||
/// </returns>
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Umbraco.Core.Composing.CompositionRoots
|
||||
container.Register(factory => factory.GetInstance<IUmbracoSettingsSection>().Templates);
|
||||
container.Register(factory => factory.GetInstance<IUmbracoSettingsSection>().RequestHandler);
|
||||
container.Register(factory => UmbracoConfig.For.GlobalSettings());
|
||||
container.Register(factory => UmbracoConfig.For.DashboardSettings());
|
||||
|
||||
// fixme - other sections we need to add?
|
||||
}
|
||||
|
||||
@@ -210,53 +210,55 @@ namespace Umbraco.Core.Composing
|
||||
/// 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"
|
||||
/// </remarks>
|
||||
internal static readonly string[] KnownAssemblyExclusionFilter = new[]
|
||||
{
|
||||
"mscorlib,",
|
||||
"System.",
|
||||
"Antlr3.",
|
||||
"Autofac.",
|
||||
"Autofac,",
|
||||
"Castle.",
|
||||
"ClientDependency.",
|
||||
"DataAnnotationsExtensions.",
|
||||
"DataAnnotationsExtensions,",
|
||||
"Dynamic,",
|
||||
"HtmlDiff,",
|
||||
"Iesi.Collections,",
|
||||
"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,"
|
||||
};
|
||||
internal static readonly string[] KnownAssemblyExclusionFilter = {
|
||||
"Antlr3.",
|
||||
"AutoMapper,",
|
||||
"AutoMapper.",
|
||||
"Autofac,", // DI
|
||||
"Autofac.",
|
||||
"AzureDirectory,",
|
||||
"Castle.", // DI, tests
|
||||
"ClientDependency.",
|
||||
"CookComputing.",
|
||||
"CSharpTest.", // BTree for NuCache
|
||||
"DataAnnotationsExtensions,",
|
||||
"DataAnnotationsExtensions.",
|
||||
"Dynamic,",
|
||||
"Examine,",
|
||||
"Examine.",
|
||||
"HtmlAgilityPack,",
|
||||
"HtmlAgilityPack.",
|
||||
"HtmlDiff,",
|
||||
"ICSharpCode.",
|
||||
"Iesi.Collections,", // used by NHibernate
|
||||
"LightInject.", // DI
|
||||
"LightInject,",
|
||||
"Lucene.",
|
||||
"Markdown,",
|
||||
"Microsoft.",
|
||||
"MiniProfiler,",
|
||||
"Moq,",
|
||||
"MySql.",
|
||||
"NHibernate,",
|
||||
"NHibernate.",
|
||||
"Newtonsoft.",
|
||||
"NPoco,",
|
||||
"NuGet.",
|
||||
"RouteDebugger,",
|
||||
"Semver.",
|
||||
"Serilog.",
|
||||
"Serilog,",
|
||||
"ServiceStack.",
|
||||
"SqlCE4Umbraco,",
|
||||
"Superpower,", // used by Serilog
|
||||
"System.",
|
||||
"TidyNet,",
|
||||
"TidyNet.",
|
||||
"WebDriver,",
|
||||
"itextsharp,",
|
||||
"mscorlib,",
|
||||
"nunit.framework,",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Finds any classes derived from the type T that contain the attribute TAttribute
|
||||
|
||||
@@ -520,6 +520,8 @@ namespace Umbraco.Core.Composing
|
||||
// if not caching, or not IDiscoverable, directly get types
|
||||
if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false)
|
||||
{
|
||||
_logger.Logger.Debug<TypeLoader>("Running a full, non-cached, scan for type {TypeName} (slow).", typeof(T).FullName);
|
||||
|
||||
return GetTypesInternal(
|
||||
typeof (T), null,
|
||||
() => TypeFinder.FindClassesOfType<T>(specificAssemblies ?? AssembliesToScan),
|
||||
@@ -559,6 +561,8 @@ namespace Umbraco.Core.Composing
|
||||
// if not caching, or not IDiscoverable, directly get types
|
||||
if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false)
|
||||
{
|
||||
_logger.Logger.Debug<TypeLoader>("Running a full, non-cached, scan for type {TypeName} / attribute {AttributeName} (slow).", typeof(T).FullName, typeof(TAttribute).FullName);
|
||||
|
||||
return GetTypesInternal(
|
||||
typeof (T), typeof (TAttribute),
|
||||
() => TypeFinder.FindClassesOfTypeWithAttribute<T, TAttribute>(specificAssemblies ?? AssembliesToScan),
|
||||
@@ -595,6 +599,11 @@ namespace Umbraco.Core.Composing
|
||||
// do not cache anything from specific assemblies
|
||||
cache &= specificAssemblies == null;
|
||||
|
||||
if (cache == false)
|
||||
{
|
||||
_logger.Logger.Debug<TypeLoader>("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", typeof(TAttribute).FullName);
|
||||
}
|
||||
|
||||
return GetTypesInternal(
|
||||
typeof (object), typeof (TAttribute),
|
||||
() => TypeFinder.FindClassesWithAttribute<TAttribute>(specificAssemblies ?? AssembliesToScan),
|
||||
|
||||
@@ -7,26 +7,22 @@ namespace Umbraco.Core.Configuration.Dashboard
|
||||
internal class AccessElement : RawXmlConfigurationElement, IAccess
|
||||
{
|
||||
public AccessElement()
|
||||
{
|
||||
|
||||
}
|
||||
{ }
|
||||
|
||||
public AccessElement(XElement rawXml)
|
||||
:base(rawXml)
|
||||
{
|
||||
}
|
||||
: base(rawXml)
|
||||
{ }
|
||||
|
||||
public IEnumerable<IAccessItem> Rules
|
||||
public IEnumerable<IAccessRule> Rules
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new List<AccessItem>();
|
||||
if (RawXml != null)
|
||||
{
|
||||
result.AddRange(RawXml.Elements("deny").Select(x => new AccessItem {Action = AccessType.Deny, Value = x.Value }));
|
||||
result.AddRange(RawXml.Elements("grant").Select(x => new AccessItem { Action = AccessType.Grant, Value = x.Value }));
|
||||
result.AddRange(RawXml.Elements("grantBySection").Select(x => new AccessItem { Action = AccessType.GrantBySection, Value = x.Value }));
|
||||
}
|
||||
var result = new List<AccessRule>();
|
||||
if (RawXml == null) return result;
|
||||
|
||||
result.AddRange(RawXml.Elements("deny").Select(x => new AccessRule {Type = AccessRuleType.Deny, Value = x.Value }));
|
||||
result.AddRange(RawXml.Elements("grant").Select(x => new AccessRule { Type = AccessRuleType.Grant, Value = x.Value }));
|
||||
result.AddRange(RawXml.Elements("grantBySection").Select(x => new AccessRule { Type = AccessRuleType.GrantBySection, Value = x.Value }));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Umbraco.Core.Configuration.Dashboard
|
||||
{
|
||||
internal class AccessItem : IAccessItem
|
||||
{
|
||||
/// <summary>
|
||||
/// This can be grant, deny or grantBySection
|
||||
/// </summary>
|
||||
public AccessType Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value of the action
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Umbraco.Core.Configuration.Dashboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements <see cref="IAccessRule"/>.
|
||||
/// </summary>
|
||||
internal class AccessRule : IAccessRule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public AccessRuleType Type { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Umbraco.Core.Configuration.Dashboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines dashboard access rules type.
|
||||
/// </summary>
|
||||
public enum AccessRuleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown (default value).
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Grant access to the dashboard if user belongs to the specified user group.
|
||||
/// </summary>
|
||||
Grant,
|
||||
|
||||
/// <summary>
|
||||
/// Deny access to the dashboard if user belongs to the specified user group.
|
||||
/// </summary>
|
||||
Deny,
|
||||
|
||||
/// <summary>
|
||||
/// Grant access to the dashboard if user has access to the specified section.
|
||||
/// </summary>
|
||||
GrantBySection
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Umbraco.Core.Configuration.Dashboard
|
||||
{
|
||||
public enum AccessType
|
||||
{
|
||||
Grant,
|
||||
Deny,
|
||||
GrantBySection
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -8,33 +7,12 @@ namespace Umbraco.Core.Configuration.Dashboard
|
||||
|
||||
internal class ControlElement : RawXmlConfigurationElement, IDashboardControl
|
||||
{
|
||||
public bool ShowOnce
|
||||
{
|
||||
get
|
||||
{
|
||||
return RawXml.Attribute("showOnce") == null
|
||||
? false
|
||||
: bool.Parse(RawXml.Attribute("showOnce").Value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddPanel
|
||||
{
|
||||
get
|
||||
{
|
||||
return RawXml.Attribute("addPanel") == null
|
||||
? true
|
||||
: bool.Parse(RawXml.Attribute("addPanel").Value);
|
||||
}
|
||||
}
|
||||
|
||||
public string PanelCaption
|
||||
{
|
||||
get
|
||||
{
|
||||
return RawXml.Attribute("panelCaption") == null
|
||||
? ""
|
||||
: RawXml.Attribute("panelCaption").Value;
|
||||
var panelCaption = RawXml.Attribute("panelCaption");
|
||||
return panelCaption == null ? "" : panelCaption.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +21,7 @@ namespace Umbraco.Core.Configuration.Dashboard
|
||||
get
|
||||
{
|
||||
var access = RawXml.Element("access");
|
||||
if (access == null)
|
||||
{
|
||||
return new AccessElement();
|
||||
}
|
||||
return new AccessElement(access);
|
||||
return access == null ? new AccessElement() : new AccessElement(access);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,10 +39,6 @@ namespace Umbraco.Core.Configuration.Dashboard
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IAccess IDashboardControl.AccessRights
|
||||
{
|
||||
get { return Access; }
|
||||
}
|
||||
IAccess IDashboardControl.AccessRights => Access;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace Umbraco.Core.Configuration.Dashboard
|
||||
{
|
||||
public interface IAccess
|
||||
{
|
||||
IEnumerable<IAccessItem> Rules { get; }
|
||||
IEnumerable<IAccessRule> Rules { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Umbraco.Core.Configuration.Dashboard
|
||||
{
|
||||
public interface IAccessItem
|
||||
{
|
||||
/// <summary>
|
||||
/// This can be grant, deny or grantBySection
|
||||
/// </summary>
|
||||
AccessType Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value of the action
|
||||
/// </summary>
|
||||
string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Umbraco.Core.Configuration.Dashboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an access rule.
|
||||
/// </summary>
|
||||
public interface IAccessRule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the rule type.
|
||||
/// </summary>
|
||||
AccessRuleType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value for the rule.
|
||||
/// </summary>
|
||||
string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,6 @@
|
||||
{
|
||||
public interface IDashboardControl
|
||||
{
|
||||
bool ShowOnce { get; }
|
||||
|
||||
bool AddPanel { get; }
|
||||
|
||||
string PanelCaption { get; }
|
||||
|
||||
string ControlPath { get; }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Umbraco.Core.Configuration
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains general settings information for the entire Umbraco instance based on information from web.config appsettings
|
||||
@@ -24,6 +26,8 @@
|
||||
/// <remarks>
|
||||
/// Defaults to ~/App_Data/umbraco.config
|
||||
/// </remarks>
|
||||
//fixme - remove
|
||||
[Obsolete("This should not be used, need to remove the content xml cache")]
|
||||
string ContentXmlFile { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -10,37 +10,61 @@ namespace Umbraco.Core.Configuration
|
||||
/// </summary>
|
||||
public static class UmbracoVersion
|
||||
{
|
||||
// BEWARE!
|
||||
// This class is parsed and updated by the build scripts.
|
||||
// Do NOT modify it unless you understand what you are doing.
|
||||
static UmbracoVersion()
|
||||
{
|
||||
var umbracoCoreAssembly = typeof(UmbracoVersion).Assembly;
|
||||
|
||||
// gets the value indicated by the AssemblyVersion attribute
|
||||
AssemblyVersion = umbracoCoreAssembly.GetName().Version;
|
||||
|
||||
// gets the value indicated by the AssemblyFileVersion attribute
|
||||
AssemblyFileVersion = System.Version.Parse(umbracoCoreAssembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version);
|
||||
|
||||
// gets the value indicated by the AssemblyInformationalVersion attribute
|
||||
// this is the true semantic version of the Umbraco Cms
|
||||
SemanticVersion = SemVersion.Parse(umbracoCoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion);
|
||||
|
||||
// gets the non-semantic version
|
||||
Current = SemanticVersion.GetVersion(3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the executing code.
|
||||
/// Gets the non-semantic version of the Umbraco code.
|
||||
/// </summary>
|
||||
public static Version Current { get; } = new Version("8.0.0");
|
||||
// TODO rename to Version
|
||||
public static Version Current { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version comment of the executing code (eg "beta").
|
||||
/// Gets the semantic version comments of the Umbraco code.
|
||||
/// </summary>
|
||||
public static string CurrentComment => "alpha.52";
|
||||
public static string Comment => SemanticVersion.Prerelease;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly version of Umbraco.Code.dll.
|
||||
/// Gets the assembly version of the Umbraco code.
|
||||
/// </summary>
|
||||
/// <remarks>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?</remarks>
|
||||
public static string AssemblyVersion => new AssemblyName(typeof(UmbracoVersion).Assembly.FullName).Version.ToString();
|
||||
/// <remarks>
|
||||
/// <para>The assembly version is the value of the <see cref="AssemblyVersionAttribute"/>.</para>
|
||||
/// <para>Is is the one that the CLR checks for compatibility. Therefore, it changes only on
|
||||
/// hard-breaking changes (for instance, on new major versions).</para>
|
||||
/// </remarks>
|
||||
public static Version AssemblyVersion {get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the semantic version of the executing code.
|
||||
/// Gets the assembly file version of the Umbraco code.
|
||||
/// </summary>
|
||||
public static SemVersion SemanticVersion { get; } = new SemVersion(
|
||||
Current.Major,
|
||||
Current.Minor,
|
||||
Current.Build,
|
||||
CurrentComment.IsNullOrWhiteSpace() ? null : CurrentComment,
|
||||
Current.Revision > 0 ? Current.Revision.ToInvariantString() : null);
|
||||
/// <remarks>
|
||||
/// <para>The assembly version is the value of the <see cref="AssemblyFileVersionAttribute"/>.</para>
|
||||
/// </remarks>
|
||||
public static Version AssemblyFileVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the semantic version of the Umbraco code.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The semantic version is the value of the <see cref="AssemblyInformationalVersionAttribute"/>.</para>
|
||||
/// <para>It is the full version of Umbraco, including comments.</para>
|
||||
/// </remarks>
|
||||
public static SemVersion SemanticVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the "local" version of the site.
|
||||
@@ -51,7 +75,7 @@ namespace Umbraco.Core.Configuration
|
||||
/// and changes during an upgrade. The executing code version changes when new code is
|
||||
/// deployed. The site/files version changes during an upgrade.</para>
|
||||
/// </remarks>
|
||||
public static SemVersion Local
|
||||
public static SemVersion LocalVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class UmbracoIndexes
|
||||
{
|
||||
public const string InternalIndexName = InternalIndexPath + "Index";
|
||||
public const string ExternalIndexName = ExternalIndexPath + "Index";
|
||||
public const string MembersIndexName = MembersIndexPath + "Index";
|
||||
|
||||
public const string InternalIndexPath = "Internal";
|
||||
public const string ExternalIndexPath = "External";
|
||||
public const string MembersIndexPath = "Members";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,49 @@ namespace Umbraco.Core
|
||||
|
||||
#region IContent
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of the Content
|
||||
/// </summary>
|
||||
public static ContentStatus GetStatus(this IContent content, string culture = null)
|
||||
{
|
||||
if (content.Trashed)
|
||||
return ContentStatus.Trashed;
|
||||
|
||||
if (!content.ContentType.VariesByCulture())
|
||||
culture = string.Empty;
|
||||
else if (culture.IsNullOrWhiteSpace())
|
||||
throw new ArgumentNullException($"{nameof(culture)} cannot be null or empty");
|
||||
|
||||
var expires = content.ContentSchedule.GetSchedule(culture, ContentScheduleAction.Expire);
|
||||
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date))
|
||||
return ContentStatus.Expired;
|
||||
|
||||
var release = content.ContentSchedule.GetSchedule(culture, ContentScheduleAction.Release);
|
||||
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now))
|
||||
return ContentStatus.AwaitingRelease;
|
||||
|
||||
if (content.Published)
|
||||
return ContentStatus.Published;
|
||||
|
||||
return ContentStatus.Unpublished;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cultures that have been flagged for unpublishing.
|
||||
/// </summary>
|
||||
/// <remarks>Gets cultures for which content.UnpublishCulture() has been invoked.</remarks>
|
||||
internal static IReadOnlyList<string> GetCulturesUnpublishing(this IContent content)
|
||||
{
|
||||
if (!content.Published || !content.ContentType.VariesByCulture() || !content.IsPropertyDirty("PublishCultureInfos"))
|
||||
return Array.Empty<string>();
|
||||
|
||||
var culturesChanging = content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key);
|
||||
return culturesChanging
|
||||
.Where(x => !content.IsCulturePublished(x) && // is not published anymore
|
||||
content.WasCulturePublished(x)) // but was published before
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this entity was just published as part of a recent save operation (i.e. it wasn't previously published)
|
||||
/// </summary>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -14,6 +9,18 @@ namespace Umbraco.Core
|
||||
///</summary>
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps this object instance into an IEnumerable{T} consisting of a single item.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"> Type of the object. </typeparam>
|
||||
/// <param name="item"> The instance that will be wrapped. </param>
|
||||
/// <returns> An IEnumerable{T} consisting of a single item. </returns>
|
||||
public static IEnumerable<T> Yield<T>(this T item)
|
||||
{
|
||||
// see EnumeratorBenchmarks - this is faster, and allocates less, than returning an array
|
||||
yield return item;
|
||||
}
|
||||
|
||||
public static IEnumerable<IEnumerable<T>> InGroupsOf<T>(this IEnumerable<T> source, int groupSize)
|
||||
{
|
||||
if (source == null)
|
||||
|
||||
@@ -35,11 +35,7 @@ namespace Umbraco.Core.IO
|
||||
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
|
||||
@@ -129,16 +125,6 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
//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);
|
||||
@@ -154,7 +140,6 @@ namespace Umbraco.Core.IO
|
||||
var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts);
|
||||
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());
|
||||
@@ -162,7 +147,6 @@ namespace Umbraco.Core.IO
|
||||
_scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", () => 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<MediaFileSystem>();
|
||||
|
||||
@@ -8,24 +8,20 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
public static string Bin => "~/bin";
|
||||
|
||||
public static string Config => IOHelper.ReturnPath("umbracoConfigDirectory", "~/config");
|
||||
public static string Config => "~/config";
|
||||
|
||||
public static string Css => IOHelper.ReturnPath("umbracoCssDirectory", "~/css");
|
||||
public static string Data => "~/App_Data";
|
||||
|
||||
public static string Data => IOHelper.ReturnPath("umbracoStorageDirectory", "~/App_Data");
|
||||
public static string Install => "~/install";
|
||||
|
||||
public static string Install => IOHelper.ReturnPath("umbracoInstallPath", "~/install");
|
||||
//fixme: remove this
|
||||
[Obsolete("Master pages are obsolete and code should be removed")]
|
||||
public static string Masterpages => "~/masterpages";
|
||||
|
||||
//TODO: Consider removing this
|
||||
public static string Masterpages => IOHelper.ReturnPath("umbracoMasterPagesPath", "~/masterpages");
|
||||
|
||||
//NOTE: this is not configurable and shouldn't need to be
|
||||
public static string AppCode => "~/App_Code";
|
||||
|
||||
//NOTE: this is not configurable and shouldn't need to be
|
||||
public static string AppPlugins => "~/App_Plugins";
|
||||
|
||||
//NOTE: this is not configurable and shouldn't need to be
|
||||
public static string MvcViews => "~/Views";
|
||||
|
||||
public static string PartialViews => MvcViews + "/Partials/";
|
||||
@@ -36,21 +32,20 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public static string Scripts => IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts");
|
||||
|
||||
public static string StyleSheets => IOHelper.ReturnPath("umbracoStylesheetsPath", "~/css");
|
||||
public static string Css => IOHelper.ReturnPath("umbracoCssPath", "~/css");
|
||||
|
||||
public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco");
|
||||
|
||||
//TODO: Consider removing this
|
||||
public static string UserControls => IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols");
|
||||
//fixme: remove this
|
||||
[Obsolete("Usercontrols are obsolete and code should be removed")]
|
||||
public static string UserControls => "~/usercontrols";
|
||||
|
||||
[Obsolete("Only used by legacy load balancing which is obsolete and should be removed")]
|
||||
public static string WebServices => IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices");
|
||||
|
||||
//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 => 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 => Data + IOHelper.DirSepChar + "preview";
|
||||
|
||||
private static string _root;
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a json read converter for <see cref="IContentAppDefinition"/>.
|
||||
/// </summary>
|
||||
internal class ContentAppDefinitionConverter : JsonReadConverter<IContentAppDefinition>
|
||||
{
|
||||
protected override IContentAppDefinition Create(Type objectType, string path, JObject jObject)
|
||||
=> new ManifestContentAppDefinition();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Configuration.Dashboard;
|
||||
using Umbraco.Core.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a json read converter for <see cref="IAccessRule"/>.
|
||||
/// </summary>
|
||||
internal class DashboardAccessRuleConverter : JsonReadConverter<IAccessRule>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override IAccessRule Create(Type objectType, string path, JObject jObject)
|
||||
{
|
||||
return new AccessRule();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deserialize(JObject jobject, IAccessRule target, JsonSerializer serializer)
|
||||
{
|
||||
// see Create above, target is either DataEditor (parameter) or ConfiguredDataEditor (property)
|
||||
|
||||
if (!(target is AccessRule accessRule))
|
||||
throw new Exception("panic.");
|
||||
|
||||
GetRule(accessRule, jobject, "grant", AccessRuleType.Grant);
|
||||
GetRule(accessRule, jobject, "deny", AccessRuleType.Deny);
|
||||
GetRule(accessRule, jobject, "grantBySection", AccessRuleType.GrantBySection);
|
||||
|
||||
if (accessRule.Type == AccessRuleType.Unknown) throw new InvalidOperationException("Rule is not defined.");
|
||||
}
|
||||
|
||||
private void GetRule(AccessRule rule, JObject jobject, string name, AccessRuleType type)
|
||||
{
|
||||
var token = jobject[name];
|
||||
if (token == null) return;
|
||||
if (rule.Type != AccessRuleType.Unknown) throw new InvalidOperationException("Multiple definition of a rule.");
|
||||
if (token.Type != JTokenType.String) throw new InvalidOperationException("Rule value is not a string.");
|
||||
rule.Type = type;
|
||||
rule.Value = token.Value<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,9 @@ namespace Umbraco.Core.Manifest
|
||||
/// Represents a content app definition, parsed from a manifest.
|
||||
/// </summary>
|
||||
[DataContract(Name = "appdef", Namespace = "")]
|
||||
public class ManifestContentAppDefinition : IContentAppDefinition
|
||||
public class ManifestContentAppDefinition
|
||||
{
|
||||
private string _view;
|
||||
private ContentApp _app;
|
||||
private ShowRule[] _showRules;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the content app.
|
||||
@@ -83,132 +81,5 @@ namespace Umbraco.Core.Manifest
|
||||
[DataMember(Name = "show")]
|
||||
public string[] Show { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ContentApp GetContentAppFor(object o, IEnumerable<IReadOnlyUserGroup> userGroups)
|
||||
{
|
||||
string partA, partB;
|
||||
|
||||
switch (o)
|
||||
{
|
||||
case IContent content:
|
||||
partA = "content";
|
||||
partB = content.ContentType.Alias;
|
||||
break;
|
||||
|
||||
case IMedia media:
|
||||
partA = "media";
|
||||
partB = media.ContentType.Alias;
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
var rules = _showRules ?? (_showRules = ShowRule.Parse(Show).ToArray());
|
||||
var userGroupsList = userGroups.ToList();
|
||||
|
||||
var okRole = false;
|
||||
var hasRole = false;
|
||||
var okType = false;
|
||||
var hasType = false;
|
||||
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
if (rule.PartA.InvariantEquals("role"))
|
||||
{
|
||||
// if roles have been ok-ed already, skip the rule
|
||||
if (okRole)
|
||||
continue;
|
||||
|
||||
// remember we have role rules
|
||||
hasRole = true;
|
||||
|
||||
foreach (var group in userGroupsList)
|
||||
{
|
||||
// if the entry does not apply, skip
|
||||
if (!rule.Matches("role", group.Alias))
|
||||
continue;
|
||||
|
||||
// if the entry applies,
|
||||
// if it's an exclude entry, exit, do not display the content app
|
||||
if (!rule.Show)
|
||||
return null;
|
||||
|
||||
// else ok to display, remember roles are ok, break from userGroupsList
|
||||
okRole = rule.Show;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // it is a type rule
|
||||
{
|
||||
// if type has been ok-ed already, skip the rule
|
||||
if (okType)
|
||||
continue;
|
||||
|
||||
// remember we have type rules
|
||||
hasType = true;
|
||||
|
||||
// if the entry does not apply, skip it
|
||||
if (!rule.Matches(partA, partB))
|
||||
continue;
|
||||
|
||||
// if the entry applies,
|
||||
// if it's an exclude entry, exit, do not display the content app
|
||||
if (!rule.Show)
|
||||
return null;
|
||||
|
||||
// else ok to display, remember type rules are ok
|
||||
okType = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if roles rules are specified but not ok,
|
||||
// or if type roles are specified but not ok,
|
||||
// cannot display the content app
|
||||
if ((hasRole && !okRole) || (hasType && !okType))
|
||||
return null;
|
||||
|
||||
// else
|
||||
// content app can be displayed
|
||||
return _app ?? (_app = new ContentApp
|
||||
{
|
||||
Alias = Alias,
|
||||
Name = Name,
|
||||
Icon = Icon,
|
||||
View = View,
|
||||
Weight = Weight
|
||||
});
|
||||
}
|
||||
|
||||
private class ShowRule
|
||||
{
|
||||
private static readonly Regex ShowRegex = new Regex("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public bool Show { get; private set; }
|
||||
public string PartA { get; private set; }
|
||||
public string PartB { get; private set; }
|
||||
|
||||
public bool Matches(string partA, string partB)
|
||||
{
|
||||
return (PartA == "*" || PartA.InvariantEquals(partA)) && (PartB == "*" || PartB.InvariantEquals(partB));
|
||||
}
|
||||
|
||||
public static IEnumerable<ShowRule> Parse(string[] rules)
|
||||
{
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
var match = ShowRegex.Match(rule);
|
||||
if (!match.Success)
|
||||
throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest.");
|
||||
|
||||
yield return new ShowRule
|
||||
{
|
||||
Show = match.Groups[1].Value != "-",
|
||||
PartA = match.Groups[2].Value,
|
||||
PartB = match.Groups[3].Value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
// contentApps: [
|
||||
// {
|
||||
// name: 'App Name', // required
|
||||
// alias: 'appAlias', // required
|
||||
// weight: 0, // optional, default is 0, use values between -99 and +99
|
||||
// icon: 'icon.app', // required
|
||||
// view: 'path/view.htm', // required
|
||||
// show: [ // optional, default is always show
|
||||
// '-content/foo', // hide for content type 'foo'
|
||||
// '+content/*', // show for all other content types
|
||||
// '+media/*', // show for all media types
|
||||
// '+role/admin' // show for admin users. Role based permissions will override others.
|
||||
// ]
|
||||
// },
|
||||
// ...
|
||||
// ]
|
||||
|
||||
/// <summary>
|
||||
/// Represents a content app factory, for content apps parsed from the manifest.
|
||||
/// </summary>
|
||||
public class ManifestContentAppFactory : IContentAppFactory
|
||||
{
|
||||
private readonly ManifestContentAppDefinition _definition;
|
||||
|
||||
public ManifestContentAppFactory(ManifestContentAppDefinition definition)
|
||||
{
|
||||
_definition = definition;
|
||||
}
|
||||
|
||||
private ContentApp _app;
|
||||
private ShowRule[] _showRules;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ContentApp GetContentAppFor(object o,IEnumerable<IReadOnlyUserGroup> userGroups)
|
||||
{
|
||||
string partA, partB;
|
||||
|
||||
switch (o)
|
||||
{
|
||||
case IContent content:
|
||||
partA = "content";
|
||||
partB = content.ContentType.Alias;
|
||||
break;
|
||||
|
||||
case IMedia media:
|
||||
partA = "media";
|
||||
partB = media.ContentType.Alias;
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
var rules = _showRules ?? (_showRules = ShowRule.Parse(_definition.Show).ToArray());
|
||||
var userGroupsList = userGroups.ToList();
|
||||
|
||||
var okRole = false;
|
||||
var hasRole = false;
|
||||
var okType = false;
|
||||
var hasType = false;
|
||||
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
if (rule.PartA.InvariantEquals("role"))
|
||||
{
|
||||
// if roles have been ok-ed already, skip the rule
|
||||
if (okRole)
|
||||
continue;
|
||||
|
||||
// remember we have role rules
|
||||
hasRole = true;
|
||||
|
||||
foreach (var group in userGroupsList)
|
||||
{
|
||||
// if the entry does not apply, skip
|
||||
if (!rule.Matches("role", group.Alias))
|
||||
continue;
|
||||
|
||||
// if the entry applies,
|
||||
// if it's an exclude entry, exit, do not display the content app
|
||||
if (!rule.Show)
|
||||
return null;
|
||||
|
||||
// else ok to display, remember roles are ok, break from userGroupsList
|
||||
okRole = rule.Show;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // it is a type rule
|
||||
{
|
||||
// if type has been ok-ed already, skip the rule
|
||||
if (okType)
|
||||
continue;
|
||||
|
||||
// remember we have type rules
|
||||
hasType = true;
|
||||
|
||||
// if the entry does not apply, skip it
|
||||
if (!rule.Matches(partA, partB))
|
||||
continue;
|
||||
|
||||
// if the entry applies,
|
||||
// if it's an exclude entry, exit, do not display the content app
|
||||
if (!rule.Show)
|
||||
return null;
|
||||
|
||||
// else ok to display, remember type rules are ok
|
||||
okType = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if roles rules are specified but not ok,
|
||||
// or if type roles are specified but not ok,
|
||||
// cannot display the content app
|
||||
if ((hasRole && !okRole) || (hasType && !okType))
|
||||
return null;
|
||||
|
||||
// else
|
||||
// content app can be displayed
|
||||
return _app ?? (_app = new ContentApp
|
||||
{
|
||||
Alias = _definition.Alias,
|
||||
Name = _definition.Name,
|
||||
Icon = _definition.Icon,
|
||||
View = _definition.View,
|
||||
Weight = _definition.Weight
|
||||
});
|
||||
}
|
||||
|
||||
private class ShowRule
|
||||
{
|
||||
private static readonly Regex ShowRegex = new Regex("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public bool Show { get; private set; }
|
||||
public string PartA { get; private set; }
|
||||
public string PartB { get; private set; }
|
||||
|
||||
public bool Matches(string partA, string partB)
|
||||
{
|
||||
return (PartA == "*" || PartA.InvariantEquals(partA)) && (PartB == "*" || PartB.InvariantEquals(partB));
|
||||
}
|
||||
|
||||
public static IEnumerable<ShowRule> Parse(string[] rules)
|
||||
{
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
var match = ShowRegex.Match(rule);
|
||||
if (!match.Success)
|
||||
throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest.");
|
||||
|
||||
yield return new ShowRule
|
||||
{
|
||||
Show = match.Groups[1].Value != "-",
|
||||
PartA = match.Groups[2].Value,
|
||||
PartB = match.Groups[3].Value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration.Dashboard;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
public class ManifestDashboardDefinition
|
||||
{
|
||||
private string _view;
|
||||
|
||||
[JsonProperty("name", Required = Required.Always)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("alias", Required = Required.Always)]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[JsonProperty("weight", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
|
||||
[DefaultValue(100)]
|
||||
public int Weight { get; set; }
|
||||
|
||||
[JsonProperty("view", Required = Required.Always)]
|
||||
public string View
|
||||
{
|
||||
get => _view;
|
||||
set => _view = IOHelper.ResolveVirtualUrl(value);
|
||||
}
|
||||
|
||||
[JsonProperty("sections")]
|
||||
public string[] Sections { get; set; } = Array.Empty<string>();
|
||||
|
||||
[JsonProperty("access")]
|
||||
public IAccessRule[] AccessRules { get; set; } = Array.Empty<IAccessRule>();
|
||||
}
|
||||
}
|
||||
@@ -1,177 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the Main.js file and replaces all tokens accordingly.
|
||||
/// </summary>
|
||||
public class ManifestParser
|
||||
{
|
||||
private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
|
||||
|
||||
private readonly IRuntimeCacheProvider _cache;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ManifestValueValidatorCollection _validators;
|
||||
|
||||
private string _path;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManifestParser"/> class.
|
||||
/// </summary>
|
||||
public ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, ILogger logger)
|
||||
: this(cache, validators, "~/App_Plugins", logger)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManifestParser"/> class.
|
||||
/// </summary>
|
||||
private ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, string path, ILogger logger)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all manifests, merged into a single manifest object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public PackageManifest Manifest
|
||||
=> _cache.GetCacheItem<PackageManifest>("Umbraco.Core.Manifest.ManifestParser::Manifests", () =>
|
||||
{
|
||||
var manifests = GetManifests();
|
||||
return MergeManifests(manifests);
|
||||
}, new TimeSpan(0, 4, 0));
|
||||
|
||||
/// <summary>
|
||||
/// Gets all manifests.
|
||||
/// </summary>
|
||||
private IEnumerable<PackageManifest> GetManifests()
|
||||
{
|
||||
var manifests = new List<PackageManifest>();
|
||||
|
||||
foreach (var path in GetManifestFiles())
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(path);
|
||||
text = TrimPreamble(text);
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
continue;
|
||||
var manifest = ParseManifest(text);
|
||||
manifests.Add(manifest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error<ManifestParser>(e, "Failed to parse manifest at '{Path}', ignoring.", path);
|
||||
}
|
||||
}
|
||||
|
||||
return manifests;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges all manifests into one.
|
||||
/// </summary>
|
||||
private static PackageManifest MergeManifests(IEnumerable<PackageManifest> manifests)
|
||||
{
|
||||
var scripts = new HashSet<string>();
|
||||
var stylesheets = new HashSet<string>();
|
||||
var propertyEditors = new List<IDataEditor>();
|
||||
var parameterEditors = new List<IDataEditor>();
|
||||
var gridEditors = new List<GridEditor>();
|
||||
var contentApps = new List<IContentAppDefinition>();
|
||||
|
||||
foreach (var manifest in manifests)
|
||||
{
|
||||
if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script);
|
||||
if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet);
|
||||
if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors);
|
||||
if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors);
|
||||
if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors);
|
||||
if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps);
|
||||
}
|
||||
|
||||
return new PackageManifest
|
||||
{
|
||||
Scripts = scripts.ToArray(),
|
||||
Stylesheets = stylesheets.ToArray(),
|
||||
PropertyEditors = propertyEditors.ToArray(),
|
||||
ParameterEditors = parameterEditors.ToArray(),
|
||||
GridEditors = gridEditors.ToArray(),
|
||||
ContentApps = contentApps.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
// gets all manifest files (recursively)
|
||||
private IEnumerable<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a manifest.
|
||||
/// </summary>
|
||||
internal PackageManifest ParseManifest(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
throw new ArgumentNullOrEmptyException(nameof(text));
|
||||
|
||||
var manifest = JsonConvert.DeserializeObject<PackageManifest>(text,
|
||||
new DataEditorConverter(_logger),
|
||||
new ValueValidatorConverter(_validators),
|
||||
new ContentAppDefinitionConverter());
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// purely for tests
|
||||
internal IEnumerable<GridEditor> ParseGridEditors(string text)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<IEnumerable<GridEditor>>(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the Main.js file and replaces all tokens accordingly.
|
||||
/// </summary>
|
||||
public class ManifestParser
|
||||
{
|
||||
private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
|
||||
|
||||
private readonly IRuntimeCacheProvider _cache;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ManifestValueValidatorCollection _validators;
|
||||
|
||||
private string _path;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManifestParser"/> class.
|
||||
/// </summary>
|
||||
public ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, ILogger logger)
|
||||
: this(cache, validators, "~/App_Plugins", logger)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManifestParser"/> class.
|
||||
/// </summary>
|
||||
private ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, string path, ILogger logger)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all manifests, merged into a single manifest object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public PackageManifest Manifest
|
||||
=> _cache.GetCacheItem<PackageManifest>("Umbraco.Core.Manifest.ManifestParser::Manifests", () =>
|
||||
{
|
||||
var manifests = GetManifests();
|
||||
return MergeManifests(manifests);
|
||||
}, new TimeSpan(0, 4, 0));
|
||||
|
||||
/// <summary>
|
||||
/// Gets all manifests.
|
||||
/// </summary>
|
||||
private IEnumerable<PackageManifest> GetManifests()
|
||||
{
|
||||
var manifests = new List<PackageManifest>();
|
||||
|
||||
foreach (var path in GetManifestFiles())
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(path);
|
||||
text = TrimPreamble(text);
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
continue;
|
||||
var manifest = ParseManifest(text);
|
||||
manifests.Add(manifest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error<ManifestParser>(e, "Failed to parse manifest at '{Path}', ignoring.", path);
|
||||
}
|
||||
}
|
||||
|
||||
return manifests;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges all manifests into one.
|
||||
/// </summary>
|
||||
private static PackageManifest MergeManifests(IEnumerable<PackageManifest> manifests)
|
||||
{
|
||||
var scripts = new HashSet<string>();
|
||||
var stylesheets = new HashSet<string>();
|
||||
var propertyEditors = new List<IDataEditor>();
|
||||
var parameterEditors = new List<IDataEditor>();
|
||||
var gridEditors = new List<GridEditor>();
|
||||
var contentApps = new List<ManifestContentAppDefinition>();
|
||||
var dashboards = new List<ManifestDashboardDefinition>();
|
||||
|
||||
foreach (var manifest in manifests)
|
||||
{
|
||||
if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script);
|
||||
if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet);
|
||||
if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors);
|
||||
if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors);
|
||||
if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors);
|
||||
if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps);
|
||||
if (manifest.Dashboards != null) dashboards.AddRange(manifest.Dashboards);
|
||||
}
|
||||
|
||||
return new PackageManifest
|
||||
{
|
||||
Scripts = scripts.ToArray(),
|
||||
Stylesheets = stylesheets.ToArray(),
|
||||
PropertyEditors = propertyEditors.ToArray(),
|
||||
ParameterEditors = parameterEditors.ToArray(),
|
||||
GridEditors = gridEditors.ToArray(),
|
||||
ContentApps = contentApps.ToArray(),
|
||||
Dashboards = dashboards.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
// gets all manifest files (recursively)
|
||||
private IEnumerable<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a manifest.
|
||||
/// </summary>
|
||||
internal PackageManifest ParseManifest(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
throw new ArgumentNullOrEmptyException(nameof(text));
|
||||
|
||||
var manifest = JsonConvert.DeserializeObject<PackageManifest>(text,
|
||||
new DataEditorConverter(_logger),
|
||||
new ValueValidatorConverter(_validators),
|
||||
new DashboardAccessRuleConverter());
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// purely for tests
|
||||
internal IEnumerable<GridEditor> ParseGridEditors(string text)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<IEnumerable<GridEditor>>(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the content of a package manifest.
|
||||
/// </summary>
|
||||
public class PackageManifest
|
||||
{
|
||||
[JsonProperty("javascript")]
|
||||
public string[] Scripts { get; set; } = Array.Empty<string>();
|
||||
|
||||
[JsonProperty("css")]
|
||||
public string[] Stylesheets { get; set; }= Array.Empty<string>();
|
||||
|
||||
[JsonProperty("propertyEditors")]
|
||||
public IDataEditor[] PropertyEditors { get; set; } = Array.Empty<IDataEditor>();
|
||||
|
||||
[JsonProperty("parameterEditors")]
|
||||
public IDataEditor[] ParameterEditors { get; set; } = Array.Empty<IDataEditor>();
|
||||
|
||||
[JsonProperty("gridEditors")]
|
||||
public GridEditor[] GridEditors { get; set; } = Array.Empty<GridEditor>();
|
||||
|
||||
[JsonProperty("contentApps")]
|
||||
public IContentAppDefinition[] ContentApps { get; set; } = Array.Empty<IContentAppDefinition>();
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the content of a package manifest.
|
||||
/// </summary>
|
||||
public class PackageManifest
|
||||
{
|
||||
[JsonProperty("javascript")]
|
||||
public string[] Scripts { get; set; } = Array.Empty<string>();
|
||||
|
||||
[JsonProperty("css")]
|
||||
public string[] Stylesheets { get; set; }= Array.Empty<string>();
|
||||
|
||||
[JsonProperty("propertyEditors")]
|
||||
public IDataEditor[] PropertyEditors { get; set; } = Array.Empty<IDataEditor>();
|
||||
|
||||
[JsonProperty("parameterEditors")]
|
||||
public IDataEditor[] ParameterEditors { get; set; } = Array.Empty<IDataEditor>();
|
||||
|
||||
[JsonProperty("gridEditors")]
|
||||
public GridEditor[] GridEditors { get; set; } = Array.Empty<GridEditor>();
|
||||
|
||||
[JsonProperty("contentApps")]
|
||||
public ManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty<ManifestContentAppDefinition>();
|
||||
|
||||
[JsonProperty("dashboards")]
|
||||
public ManifestDashboardDefinition[] Dashboards { get; set; } = Array.Empty<ManifestDashboardDefinition>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Umbraco.Core.Migrations.Expressions.Alter.Expressions;
|
||||
using Umbraco.Core.Migrations.Expressions.Common.Expressions;
|
||||
using Umbraco.Core.Migrations.Expressions.Create.Expressions;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Expressions.Alter.Table
|
||||
@@ -154,8 +155,8 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Table
|
||||
var index = new CreateIndexExpression(_context, new IndexDefinition
|
||||
{
|
||||
Name = indexName,
|
||||
TableName = Expression.TableName,
|
||||
IsUnique = true
|
||||
TableName = Expression.TableName,
|
||||
IndexType = IndexTypes.UniqueNonClustered
|
||||
});
|
||||
|
||||
index.Index.Columns.Add(new IndexColumnDefinition
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Data;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Migrations.Expressions.Common.Expressions;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Expressions.Create.Column
|
||||
@@ -112,8 +113,8 @@ namespace Umbraco.Core.Migrations.Expressions.Create.Column
|
||||
var index = new CreateIndexExpression(_context, new IndexDefinition
|
||||
{
|
||||
Name = indexName,
|
||||
TableName = Expression.TableName,
|
||||
IsUnique = true
|
||||
TableName = Expression.TableName,
|
||||
IndexType = IndexTypes.UniqueNonClustered
|
||||
});
|
||||
|
||||
index.Index.Columns.Add(new IndexColumnDefinition
|
||||
|
||||
@@ -56,41 +56,29 @@ namespace Umbraco.Core.Migrations.Expressions.Create.Index
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
{
|
||||
Expression.Index.IndexType = IndexTypes.UniqueNonClustered;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICreateIndexOnColumnBuilder NonClustered()
|
||||
{
|
||||
Expression.Index.IndexType = IndexTypes.NonClustered;
|
||||
Expression.Index.IsClustered = false;
|
||||
Expression.Index.IndexType = IndexTypes.NonClustered;
|
||||
Expression.Index.IsUnique = false;
|
||||
Expression.Index.IndexType = IndexTypes.NonClustered;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
{
|
||||
Expression.Index.IndexType = IndexTypes.Clustered;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
ICreateIndexOnColumnBuilder ICreateIndexOptionsBuilder.Unique()
|
||||
{
|
||||
Expression.Index.IndexType = IndexTypes.UniqueNonClustered;
|
||||
Expression.Index.IsUnique = true;
|
||||
Expression.Index.IndexType = IndexTypes.UniqueNonClustered;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using NPoco;
|
||||
using Umbraco.Core.Migrations.Expressions.Common.Expressions;
|
||||
using Umbraco.Core.Migrations.Expressions.Create.Expressions;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Expressions.Create.Table
|
||||
@@ -176,8 +177,8 @@ namespace Umbraco.Core.Migrations.Expressions.Create.Table
|
||||
{
|
||||
Name = indexName,
|
||||
SchemaName = Expression.SchemaName,
|
||||
TableName = Expression.TableName,
|
||||
IsUnique = true
|
||||
TableName = Expression.TableName,
|
||||
IndexType = IndexTypes.UniqueNonClustered
|
||||
});
|
||||
|
||||
index.Index.Columns.Add(new IndexColumnDefinition
|
||||
|
||||
@@ -7,6 +7,9 @@ namespace Umbraco.Core.Migrations
|
||||
/// </summary>
|
||||
public interface IMigration : IDiscoverable
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes the migration.
|
||||
/// </summary>
|
||||
void Migrate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,13 @@ namespace Umbraco.Core.Migrations
|
||||
ISqlContext SqlContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expression index.
|
||||
/// Gets or sets the expression index.
|
||||
/// </summary>
|
||||
int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether an expression is being built.
|
||||
/// </summary>
|
||||
bool BuildingExpression { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occurs when a migration exception is not executed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Migration expression such as Alter.Table(...).Do() *must* end with Do() else they are
|
||||
/// not executed. When a non-executed expression is detected, an IncompleteMigrationExpressionException
|
||||
/// is thrown.</para>
|
||||
/// </remarks>
|
||||
public class IncompleteMigrationExpressionException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IncompleteMigrationExpressionException"/> class.
|
||||
/// </summary>
|
||||
public IncompleteMigrationExpressionException()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IncompleteMigrationExpressionException"/> class with a message.
|
||||
/// </summary>
|
||||
public IncompleteMigrationExpressionException(string message)
|
||||
: base(message)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,8 @@ namespace Umbraco.Core.Migrations.Install
|
||||
typeof (ConsentDto),
|
||||
typeof (AuditEntryDto),
|
||||
typeof (ContentVersionCultureVariationDto),
|
||||
typeof (DocumentCultureVariationDto)
|
||||
typeof (DocumentCultureVariationDto),
|
||||
typeof (ContentScheduleDto)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -62,42 +62,65 @@ namespace Umbraco.Core.Migrations
|
||||
/// </summary>
|
||||
protected Sql<ISqlContext> Sql(string sql, params object[] args) => Context.SqlContext.Sql(sql, args);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Executes the migration.
|
||||
/// </summary>
|
||||
public abstract void Migrate();
|
||||
|
||||
/// <inheritdoc />
|
||||
void IMigration.Migrate()
|
||||
{
|
||||
Migrate();
|
||||
|
||||
// ensure there is no building expression
|
||||
// ie we did not forget to .Do() an expression
|
||||
if (Context.BuildingExpression)
|
||||
throw new IncompleteMigrationExpressionException("The migration has run, but leaves an expression that has not run.");
|
||||
}
|
||||
|
||||
// ensures we are not already building,
|
||||
// ie we did not forget to .Do() an expression
|
||||
private T BeginBuild<T>(T builder)
|
||||
{
|
||||
if (Context.BuildingExpression)
|
||||
throw new IncompleteMigrationExpressionException("Cannot create a new expression: the previous expression has not run.");
|
||||
Context.BuildingExpression = true;
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Alter expression.
|
||||
/// </summary>
|
||||
public IAlterBuilder Alter => new AlterBuilder(Context);
|
||||
public IAlterBuilder Alter => BeginBuild(new AlterBuilder(Context));
|
||||
|
||||
/// <summary>
|
||||
/// Builds a Create expression.
|
||||
/// </summary>
|
||||
public ICreateBuilder Create => new CreateBuilder(Context);
|
||||
public ICreateBuilder Create => BeginBuild(new CreateBuilder(Context));
|
||||
|
||||
/// <summary>
|
||||
/// Builds a Delete expression.
|
||||
/// </summary>
|
||||
public IDeleteBuilder Delete => new DeleteBuilder(Context);
|
||||
public IDeleteBuilder Delete => BeginBuild(new DeleteBuilder(Context));
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Execute expression.
|
||||
/// </summary>
|
||||
public IExecuteBuilder Execute => new ExecuteBuilder(Context);
|
||||
public IExecuteBuilder Execute => BeginBuild(new ExecuteBuilder(Context));
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Insert expression.
|
||||
/// </summary>
|
||||
public IInsertBuilder Insert => new InsertBuilder(Context);
|
||||
public IInsertBuilder Insert => BeginBuild(new InsertBuilder(Context));
|
||||
|
||||
/// <summary>
|
||||
/// Builds a Rename expression.
|
||||
/// </summary>
|
||||
public IRenameBuilder Rename => new RenameBuilder(Context);
|
||||
public IRenameBuilder Rename => BeginBuild(new RenameBuilder(Context));
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Update expression.
|
||||
/// </summary>
|
||||
public IUpdateBuilder Update => new UpdateBuilder(Context);
|
||||
public IUpdateBuilder Update => BeginBuild(new UpdateBuilder(Context));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,20 +4,33 @@ using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Core.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a migration context.
|
||||
/// </summary>
|
||||
internal class MigrationContext : IMigrationContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrationContext"/> class.
|
||||
/// </summary>
|
||||
public MigrationContext(IUmbracoDatabase database, ILogger logger)
|
||||
{
|
||||
Database = database ?? throw new ArgumentNullException(nameof(database));
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILogger Logger { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUmbracoDatabase Database { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISqlContext SqlContext => Database.SqlContext;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool BuildingExpression { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace Umbraco.Core.Migrations
|
||||
if (_executed)
|
||||
throw new InvalidOperationException("This expression has already been executed.");
|
||||
_executed = true;
|
||||
Context.BuildingExpression = false;
|
||||
|
||||
var sql = GetSql();
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
Chain<RenameTrueFalseField>("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0
|
||||
Chain<SetDefaultTagsStorageType>("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0
|
||||
//Chain<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); // stephan added that one = merge conflict, remove
|
||||
|
||||
|
||||
Chain<FallbackLanguage>("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // add andy's after others, with a new target state
|
||||
From("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}") // and provide a path out of andy's
|
||||
.CopyChain("{39E5B1F7-A50B-437E-B768-1723AEC45B65}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}", "{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // to next
|
||||
@@ -141,6 +141,9 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
Chain<AddLogTableColumns>("{8804D8E8-FE62-4E3A-B8A2-C047C2118C38}");
|
||||
Chain<DropPreValueTable>("{23275462-446E-44C7-8C2C-3B8C1127B07D}");
|
||||
Chain<DropDownPropertyEditorsMigration>("{6B251841-3069-4AD5-8AE9-861F9523E8DA}");
|
||||
Chain<TagsMigrationFix>("{EE429F1B-9B26-43CA-89F8-A86017C809A3}");
|
||||
Chain<DropTemplateDesignColumn>("{08919C4B-B431-449C-90EC-2B8445B5C6B1}");
|
||||
Chain<TablesForScheduledPublishing>("{7EB0254C-CB8B-4C75-B15B-D48C55B449EB}");
|
||||
|
||||
//FINAL
|
||||
|
||||
|
||||
@@ -1,47 +1,51 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the default storageType for the tags datatype to "CSV" to ensure backwards compatibilty since the default is going to be JSON in new versions
|
||||
/// </summary>
|
||||
/// Set the default storageType for the tags datatype to "CSV" to ensure backwards compatibility since the default is going to be JSON in new versions.
|
||||
/// </summary>
|
||||
public class SetDefaultTagsStorageType : MigrationBase
|
||||
{
|
||||
public SetDefaultTagsStorageType(IMigrationContext context) : base(context)
|
||||
{
|
||||
}
|
||||
public SetDefaultTagsStorageType(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
// dummy editor for deserialization
|
||||
private class TagConfigurationEditor : ConfigurationEditor<TagConfiguration>
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
if (Context?.Database == null) return;
|
||||
// get all Umbraco.Tags datatypes
|
||||
var dataTypeDtos = Database.Fetch<DataTypeDto>(Context.SqlContext.Sql()
|
||||
.Select<DataTypeDto>()
|
||||
.From<DataTypeDto>()
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == Constants.PropertyEditors.Aliases.Tags));
|
||||
|
||||
// We need to get all datatypes with an alias of "umbraco.tags" so we can loop over them and set the missing values if needed
|
||||
var datatypes = Context.Database.Fetch<DataTypeDto>();
|
||||
var tagsDataTypes = datatypes.Where(x => string.Equals(x.EditorAlias, Constants.PropertyEditors.Aliases.Tags, StringComparison.InvariantCultureIgnoreCase));
|
||||
// get a dummy editor for deserialization
|
||||
var editor = new TagConfigurationEditor();
|
||||
|
||||
foreach (var datatype in tagsDataTypes)
|
||||
foreach (var dataTypeDto in dataTypeDtos)
|
||||
{
|
||||
var dataTypePreValues = JsonConvert.DeserializeObject<JObject>(datatype.Configuration);
|
||||
// need to check storageType on raw dictionary, as TagConfiguration would have a default value
|
||||
var dictionary = JsonConvert.DeserializeObject<JObject>(dataTypeDto.Configuration);
|
||||
|
||||
// We need to check if the node has a "storageType" set
|
||||
if (!dataTypePreValues.ContainsKey("storageType"))
|
||||
// if missing, use TagConfiguration to properly update the configuration
|
||||
// due to ... reasons ... the key can start with a lower or upper 'S'
|
||||
if (!dictionary.ContainsKey("storageType") && !dictionary.ContainsKey("StorageType"))
|
||||
{
|
||||
dataTypePreValues["storageType"] = "Csv";
|
||||
var configuration = (TagConfiguration)editor.FromDatabase(dataTypeDto.Configuration);
|
||||
configuration.StorageType = TagsStorageType.Csv;
|
||||
dataTypeDto.Configuration = ConfigurationEditor.ToDatabase(configuration);
|
||||
Database.Update(dataTypeDto);
|
||||
}
|
||||
|
||||
Update.Table(Constants.DatabaseSchema.Tables.DataType)
|
||||
.Set(new { config = JsonConvert.SerializeObject(dataTypePreValues) })
|
||||
.Where(new { nodeId = datatype.NodeId })
|
||||
.Do();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
public class DropDownPropertyEditorsMigration : MigrationBase
|
||||
{
|
||||
public DropDownPropertyEditorsMigration(IMigrationContext context) : base(context)
|
||||
private readonly CacheRefresherCollection _cacheRefreshers;
|
||||
private readonly IServerMessenger _serverMessenger;
|
||||
|
||||
public DropDownPropertyEditorsMigration(IMigrationContext context, CacheRefresherCollection cacheRefreshers, IServerMessenger serverMessenger)
|
||||
: base(context)
|
||||
{
|
||||
_cacheRefreshers = cacheRefreshers;
|
||||
_serverMessenger = serverMessenger;
|
||||
}
|
||||
|
||||
// dummy editor for deserialization
|
||||
private class ValueListConfigurationEditor : ConfigurationEditor<ValueListConfiguration>
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
//need to convert the old drop down data types to use the new one
|
||||
var oldDropDowns = Database.Fetch<DataTypeDto>(Sql()
|
||||
var dataTypes = Database.Fetch<DataTypeDto>(Sql()
|
||||
.Select<DataTypeDto>()
|
||||
.From<DataTypeDto>()
|
||||
.Where<DataTypeDto>(x => x.EditorAlias.Contains(".DropDown")));
|
||||
foreach (var dd in oldDropDowns)
|
||||
|
||||
foreach (var dataType in dataTypes)
|
||||
{
|
||||
//nothing to change if there is no config
|
||||
if (dd.Configuration.IsNullOrWhiteSpace()) continue;
|
||||
|
||||
ValueListConfiguration config;
|
||||
try
|
||||
{
|
||||
config = JsonConvert.DeserializeObject<ValueListConfiguration>(dd.Configuration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error<DropDownPropertyEditorsMigration>(
|
||||
ex, "Invalid drop down configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared",
|
||||
dd.Configuration);
|
||||
dd.Configuration = null;
|
||||
Database.Update(dd);
|
||||
continue;
|
||||
}
|
||||
|
||||
var propDataSql = Sql().Select<PropertyDataDto>().From<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>(x => x.Id, x => x.PropertyTypeId)
|
||||
.InnerJoin<DataTypeDto>().On<DataTypeDto, PropertyTypeDto>(x => x.NodeId, x => x.DataTypeId)
|
||||
.Where<PropertyTypeDto>(x => x.DataTypeId == dd.NodeId);
|
||||
|
||||
var propDatas = Database.Query<PropertyDataDto>(propDataSql);
|
||||
var toUpdate = new List<PropertyDataDto>();
|
||||
foreach (var propData in propDatas)
|
||||
if (!dataType.Configuration.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (UpdatePropertyDataDto(propData, config))
|
||||
// parse configuration, and update everything accordingly
|
||||
try
|
||||
{
|
||||
//update later, we are iterating all values right now so no SQL can be run inside of this iteration (i.e. Query<T>)
|
||||
toUpdate.Add(propData);
|
||||
config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase(dataType.Configuration);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error<DropDownPropertyEditorsMigration>(
|
||||
ex, "Invalid drop down configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared",
|
||||
dataType.Configuration);
|
||||
|
||||
//run the property data updates
|
||||
foreach (var propData in toUpdate)
|
||||
// reset
|
||||
config = new ValueListConfiguration();
|
||||
}
|
||||
|
||||
// get property data dtos
|
||||
var propertyDataDtos = Database.Fetch<PropertyDataDto>(Sql()
|
||||
.Select<PropertyDataDto>()
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>((pt, pd) => pt.Id == pd.PropertyTypeId)
|
||||
.InnerJoin<DataTypeDto>().On<DataTypeDto, PropertyTypeDto>((dt, pt) => dt.NodeId == pt.DataTypeId)
|
||||
.Where<PropertyTypeDto>(x => x.DataTypeId == dataType.NodeId));
|
||||
|
||||
// update dtos
|
||||
var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config));
|
||||
|
||||
// persist changes
|
||||
foreach (var propertyDataDto in updatedDtos)
|
||||
Database.Update(propertyDataDto);
|
||||
}
|
||||
else
|
||||
{
|
||||
Database.Update(propData);
|
||||
// default configuration
|
||||
config = new ValueListConfiguration();
|
||||
}
|
||||
|
||||
var requiresCacheRebuild = false;
|
||||
switch (dd.EditorAlias)
|
||||
switch (dataType.EditorAlias)
|
||||
{
|
||||
case string ea when ea.InvariantEquals("Umbraco.DropDown"):
|
||||
UpdateDataType(dd, config, false);
|
||||
UpdateDataType(dataType, config, false);
|
||||
break;
|
||||
case string ea when ea.InvariantEquals("Umbraco.DropdownlistPublishingKeys"):
|
||||
UpdateDataType(dd, config, false);
|
||||
UpdateDataType(dataType, config, false);
|
||||
requiresCacheRebuild = true;
|
||||
break;
|
||||
case string ea when ea.InvariantEquals("Umbraco.DropDownMultiple"):
|
||||
UpdateDataType(dd, config, true);
|
||||
UpdateDataType(dataType, config, true);
|
||||
break;
|
||||
case string ea when ea.InvariantEquals("Umbraco.DropdownlistMultiplePublishKeys"):
|
||||
UpdateDataType(dd, config, true);
|
||||
UpdateDataType(dataType, config, true);
|
||||
requiresCacheRebuild = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (requiresCacheRebuild)
|
||||
{
|
||||
//TODO: How to force rebuild the cache?
|
||||
var dataTypeCacheRefresher = _cacheRefreshers[Guid.Parse("35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2")];
|
||||
_serverMessenger.PerformRefreshAll(dataTypeCacheRefresher);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,13 +107,15 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
private void UpdateDataType(DataTypeDto dataType, ValueListConfiguration config, bool isMultiple)
|
||||
{
|
||||
dataType.EditorAlias = Constants.PropertyEditors.Aliases.DropDownListFlexible;
|
||||
var flexConfig = new
|
||||
{
|
||||
multiple = isMultiple,
|
||||
items = config.Items
|
||||
};
|
||||
dataType.DbType = ValueStorageType.Nvarchar.ToString();
|
||||
dataType.Configuration = JsonConvert.SerializeObject(flexConfig);
|
||||
|
||||
var flexConfig = new DropDownFlexibleConfiguration
|
||||
{
|
||||
Items = config.Items,
|
||||
Multiple = isMultiple
|
||||
};
|
||||
dataType.Configuration = ConfigurationEditor.ToDatabase(flexConfig);
|
||||
|
||||
Database.Update(dataType);
|
||||
}
|
||||
|
||||
@@ -137,7 +152,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
Logger.Warn<DropDownPropertyEditorsMigration>(
|
||||
"Could not find associated data type configuration for stored Id {DataTypeId}", id);
|
||||
canConvert = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canConvert)
|
||||
{
|
||||
@@ -146,7 +161,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
propData.IntegerValue = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -167,6 +181,5 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
|
||||
public class DropTaskTables : MigrationBase
|
||||
{
|
||||
public DropTaskTables(IMigrationContext context)
|
||||
@@ -8,8 +9,10 @@
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
Delete.Table("cmsTaskType");
|
||||
Delete.Table("cmsTask");
|
||||
if (TableExists("cmsTaskType"))
|
||||
Delete.Table("cmsTaskType");
|
||||
if (TableExists("cmsTask"))
|
||||
Delete.Table("cmsTask");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
public class DropTemplateDesignColumn : MigrationBase
|
||||
{
|
||||
public DropTemplateDesignColumn(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
if(ColumnExists("cmsTemplate", "design"))
|
||||
Delete.Column("design").FromTable("cmsTemplate").Do();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using NPoco;
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
public class TablesForScheduledPublishing : MigrationBase
|
||||
{
|
||||
public TablesForScheduledPublishing(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
//Get anything currently scheduled
|
||||
var scheduleSql = new Sql()
|
||||
.Select("nodeId", "releaseDate", "expireDate")
|
||||
.From("umbracoDocument")
|
||||
.Where("releaseDate IS NOT NULL OR expireDate IS NOT NULL");
|
||||
var schedules = Database.Dictionary<int, (DateTime? releaseDate, DateTime? expireDate)> (scheduleSql);
|
||||
|
||||
//drop old cols
|
||||
Delete.Column("releaseDate").FromTable("umbracoDocument").Do();
|
||||
Delete.Column("expireDate").FromTable("umbracoDocument").Do();
|
||||
//add new table
|
||||
Create.Table<ContentScheduleDto>().Do();
|
||||
|
||||
//migrate the schedule
|
||||
foreach(var s in schedules)
|
||||
{
|
||||
var date = s.Value.releaseDate;
|
||||
var action = ContentScheduleAction.Release.ToString();
|
||||
if (!date.HasValue)
|
||||
{
|
||||
date = s.Value.expireDate;
|
||||
action = ContentScheduleAction.Expire.ToString();
|
||||
}
|
||||
|
||||
Insert.IntoTable(ContentScheduleDto.TableName)
|
||||
.Row(new { nodeId = s.Key, date = date.Value, action = action })
|
||||
.Do();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
// kill unused parentId column
|
||||
Delete.ForeignKey("FK_cmsTags_cmsTags").OnTable(Constants.DatabaseSchema.Tables.Tag).Do();
|
||||
Delete.Column("ParentId").FromTable(Constants.DatabaseSchema.Tables.Tag);
|
||||
Delete.Column("ParentId").FromTable(Constants.DatabaseSchema.Tables.Tag).Do();
|
||||
}
|
||||
}
|
||||
|
||||
// fixes TagsMigration that... originally failed to properly drop the ParentId column
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
public class TagsMigrationFix : MigrationBase
|
||||
{
|
||||
public TagsMigrationFix(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
// kill unused parentId column, if it still exists
|
||||
if (ColumnExists(Constants.DatabaseSchema.Tables.Tag, "ParentId"))
|
||||
Delete.Column("ParentId").FromTable(Constants.DatabaseSchema.Tables.Tag).Do();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,9 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
private IContentType _contentType;
|
||||
private ITemplate _template;
|
||||
private ContentScheduleCollection _schedule;
|
||||
private bool _published;
|
||||
private PublishedState _publishedState;
|
||||
private DateTime? _releaseDate;
|
||||
private DateTime? _expireDate;
|
||||
private ContentCultureInfosCollection _publishInfos;
|
||||
private ContentCultureInfosCollection _publishInfosOrig;
|
||||
private HashSet<string> _editedCultures;
|
||||
@@ -86,11 +85,43 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo<Content, ITemplate>(x => x.Template);
|
||||
public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo<Content, bool>(x => x.Published);
|
||||
public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ReleaseDate);
|
||||
public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ExpireDate);
|
||||
public readonly PropertyInfo ContentScheduleSelector = ExpressionHelper.GetPropertyInfo<Content, ContentScheduleCollection>(x => x.ContentSchedule);
|
||||
public readonly PropertyInfo PublishCultureInfosSelector = ExpressionHelper.GetPropertyInfo<Content, IReadOnlyDictionary<string, ContentCultureInfos>>(x => x.PublishCultureInfos);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DoNotClone]
|
||||
public ContentScheduleCollection ContentSchedule
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_schedule == null)
|
||||
{
|
||||
_schedule = new ContentScheduleCollection();
|
||||
_schedule.CollectionChanged += ScheduleCollectionChanged;
|
||||
}
|
||||
return _schedule;
|
||||
}
|
||||
set
|
||||
{
|
||||
if(_schedule != null)
|
||||
_schedule.CollectionChanged -= ScheduleCollectionChanged;
|
||||
SetPropertyValueAndDetectChanges(value, ref _schedule, Ps.Value.ContentScheduleSelector);
|
||||
if (_schedule != null)
|
||||
_schedule.CollectionChanged += ScheduleCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection changed event handler to ensure the schedule field is set to dirty when the schedule changes
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ScheduleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(Ps.Value.ContentScheduleSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the template used by the Content.
|
||||
/// This is used to override the default one from the ContentType.
|
||||
@@ -100,35 +131,12 @@ namespace Umbraco.Core.Models
|
||||
/// the Default template from the ContentType will be returned.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public virtual ITemplate Template
|
||||
public ITemplate Template
|
||||
{
|
||||
get => _template ?? _contentType.DefaultTemplate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of the Content
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
public ContentStatus Status
|
||||
{
|
||||
get
|
||||
{
|
||||
if(Trashed)
|
||||
return ContentStatus.Trashed;
|
||||
|
||||
if(ExpireDate.HasValue && ExpireDate.Value > DateTime.MinValue && DateTime.Now > ExpireDate.Value)
|
||||
return ContentStatus.Expired;
|
||||
|
||||
if(ReleaseDate.HasValue && ReleaseDate.Value > DateTime.MinValue && ReleaseDate.Value > DateTime.Now)
|
||||
return ContentStatus.AwaitingRelease;
|
||||
|
||||
if(Published)
|
||||
return ContentStatus.Published;
|
||||
|
||||
return ContentStatus.Unpublished;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this content item is published or not.
|
||||
@@ -169,26 +177,6 @@ namespace Umbraco.Core.Models
|
||||
[IgnoreDataMember]
|
||||
public bool Edited { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date this Content should be released and thus be published
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public DateTime? ReleaseDate
|
||||
{
|
||||
get => _releaseDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The date this Content should expire and thus be unpublished
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public DateTime? ExpireDate
|
||||
{
|
||||
get => _expireDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ContentType used by this content object
|
||||
/// </summary>
|
||||
@@ -229,7 +217,7 @@ namespace Umbraco.Core.Models
|
||||
public bool WasCulturePublished(string culture)
|
||||
// just check _publishInfosOrig - a copy of _publishInfos
|
||||
// a non-available culture could not become published anyways
|
||||
=> _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture);
|
||||
=> _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture);
|
||||
|
||||
// adjust dates to sync between version, cultures etc
|
||||
// used by the repo when persisting
|
||||
@@ -310,6 +298,9 @@ namespace Umbraco.Core.Models
|
||||
if (_publishInfos == null) return;
|
||||
_publishInfos.Remove(culture);
|
||||
if (_publishInfos.Count == 0) _publishInfos = null;
|
||||
|
||||
// set the culture to be dirty - it's been modified
|
||||
TouchCultureInfo(culture);
|
||||
}
|
||||
|
||||
// sets a publish edited
|
||||
@@ -351,7 +342,7 @@ namespace Umbraco.Core.Models
|
||||
public bool Blueprint { get; internal set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool PublishCulture(string culture = "*")
|
||||
public bool PublishCulture(string culture = "*")
|
||||
{
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
|
||||
@@ -404,7 +395,7 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void UnpublishCulture(string culture = "*")
|
||||
public void UnpublishCulture(string culture = "*")
|
||||
{
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
|
||||
@@ -520,7 +511,14 @@ namespace Umbraco.Core.Models
|
||||
clonedContent._publishInfos = (ContentCultureInfosCollection) _publishInfos.DeepClone(); //manually deep clone
|
||||
clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler
|
||||
}
|
||||
|
||||
|
||||
//if properties exist then deal with event bindings
|
||||
if (clonedContent._schedule != null)
|
||||
{
|
||||
clonedContent._schedule.CollectionChanged -= ScheduleCollectionChanged; //clear this event handler if any
|
||||
clonedContent._schedule = (ContentScheduleCollection)_schedule.DeepClone(); //manually deep clone
|
||||
clonedContent._schedule.CollectionChanged += clonedContent.ScheduleCollectionChanged; //re-assign correct event handler
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace Umbraco.Core.Models
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public virtual IReadOnlyDictionary<string, ContentCultureInfos> CultureInfos => _cultureInfos ?? NoInfos;
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCultureName(string culture)
|
||||
{
|
||||
@@ -222,6 +222,12 @@ namespace Umbraco.Core.Models
|
||||
_cultureInfos = null;
|
||||
}
|
||||
|
||||
protected void TouchCultureInfo(string culture)
|
||||
{
|
||||
if (_cultureInfos == null || !_cultureInfos.TryGetValue(culture, out var infos)) return;
|
||||
_cultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now);
|
||||
}
|
||||
|
||||
// internal for repository
|
||||
internal void SetCultureInfo(string culture, string name, DateTime date)
|
||||
{
|
||||
@@ -235,7 +241,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
_cultureInfos = new ContentCultureInfosCollection();
|
||||
_cultureInfos.CollectionChanged += CultureInfosCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
_cultureInfos.AddOrUpdate(culture, name, date);
|
||||
}
|
||||
@@ -368,7 +374,7 @@ namespace Umbraco.Core.Models
|
||||
#endregion
|
||||
|
||||
#region Validation
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Property[] ValidateProperties(string culture = "*")
|
||||
{
|
||||
@@ -496,7 +502,6 @@ namespace Umbraco.Core.Models
|
||||
clonedContent._properties = (PropertyCollection) _properties.DeepClone(); //manually deep clone
|
||||
clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ using Umbraco.Core.Models.Membership;
|
||||
namespace Umbraco.Core.Models.ContentEditing
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a content app definition.
|
||||
/// Represents a content app factory.
|
||||
/// </summary>
|
||||
public interface IContentAppDefinition
|
||||
public interface IContentAppFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the content app for an object.
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a scheduled action for a document.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public class ContentSchedule : IDeepCloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentSchedule"/> class.
|
||||
/// </summary>
|
||||
public ContentSchedule(string culture, DateTime date, ContentScheduleAction action)
|
||||
{
|
||||
Id = Guid.Empty; // will be assigned by document repository
|
||||
Culture = culture;
|
||||
Date = date;
|
||||
Action = action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentSchedule"/> class.
|
||||
/// </summary>
|
||||
public ContentSchedule(Guid id, string culture, DateTime date, ContentScheduleAction action)
|
||||
{
|
||||
Id = id;
|
||||
Culture = culture;
|
||||
Date = date;
|
||||
Action = action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the document targeted by the scheduled action.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Guid Id { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the culture of the scheduled action.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// string.Empty represents the invariant culture.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public string Culture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date of the scheduled action.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public DateTime Date { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the action to take.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public ContentScheduleAction Action { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ContentSchedule other && Equals(other);
|
||||
|
||||
public bool Equals(ContentSchedule other)
|
||||
{
|
||||
// don't compare Ids, two ContentSchedule are equal if they are for the same change
|
||||
// for the same culture, on the same date - and the collection deals w/duplicates
|
||||
return Culture.InvariantEquals(other.Culture) && Date == other.Date && Action == other.Action;
|
||||
}
|
||||
|
||||
public object DeepClone()
|
||||
{
|
||||
return new ContentSchedule(Id, Culture, Date, Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines scheduled actions for documents.
|
||||
/// </summary>
|
||||
public enum ContentScheduleAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Release the document.
|
||||
/// </summary>
|
||||
Release,
|
||||
|
||||
/// <summary>
|
||||
/// Expire the document.
|
||||
/// </summary>
|
||||
Expire
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public class ContentScheduleCollection : INotifyCollectionChanged, IDeepCloneable, IEquatable<ContentScheduleCollection>
|
||||
{
|
||||
//underlying storage for the collection backed by a sorted list so that the schedule is always in order of date and that duplicate dates per culture are not allowed
|
||||
private readonly Dictionary<string, SortedList<DateTime, ContentSchedule>> _schedule
|
||||
= new Dictionary<string, SortedList<DateTime, ContentSchedule>>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
private void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
CollectionChanged?.Invoke(this, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an existing schedule
|
||||
/// </summary>
|
||||
/// <param name="schedule"></param>
|
||||
public void Add(ContentSchedule schedule)
|
||||
{
|
||||
if (!_schedule.TryGetValue(schedule.Culture, out var changes))
|
||||
{
|
||||
changes = new SortedList<DateTime, ContentSchedule>();
|
||||
_schedule[schedule.Culture] = changes;
|
||||
}
|
||||
|
||||
//TODO: Below will throw if there are duplicate dates added, validate/return bool?
|
||||
changes.Add(schedule.Date, schedule);
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, schedule));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new schedule for invariant content
|
||||
/// </summary>
|
||||
/// <param name="releaseDate"></param>
|
||||
/// <param name="expireDate"></param>
|
||||
public bool Add(DateTime? releaseDate, DateTime? expireDate)
|
||||
{
|
||||
return Add(string.Empty, releaseDate, expireDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new schedule for a culture
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="releaseDate"></param>
|
||||
/// <param name="expireDate"></param>
|
||||
/// <returns>true if successfully added, false if validation fails</returns>
|
||||
public bool Add(string culture, DateTime? releaseDate, DateTime? expireDate)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException(nameof(culture));
|
||||
if (releaseDate.HasValue && expireDate.HasValue && releaseDate >= expireDate)
|
||||
return false;
|
||||
|
||||
if (!releaseDate.HasValue && !expireDate.HasValue) return false;
|
||||
|
||||
//TODO: Do we allow passing in a release or expiry date that is before now?
|
||||
|
||||
if (!_schedule.TryGetValue(culture, out var changes))
|
||||
{
|
||||
changes = new SortedList<DateTime, ContentSchedule>();
|
||||
_schedule[culture] = changes;
|
||||
}
|
||||
|
||||
//TODO: Below will throw if there are duplicate dates added, should validate/return bool?
|
||||
// but the bool won't indicate which date was in error, maybe have 2 diff methods to schedule start/end?
|
||||
|
||||
if (releaseDate.HasValue)
|
||||
{
|
||||
var entry = new ContentSchedule(culture, releaseDate.Value, ContentScheduleAction.Release);
|
||||
changes.Add(releaseDate.Value, entry);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry));
|
||||
}
|
||||
|
||||
if (expireDate.HasValue)
|
||||
{
|
||||
var entry = new ContentSchedule(culture, expireDate.Value, ContentScheduleAction.Expire);
|
||||
changes.Add(expireDate.Value, entry);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a scheduled change
|
||||
/// </summary>
|
||||
/// <param name="change"></param>
|
||||
public void Remove(ContentSchedule change)
|
||||
{
|
||||
if (_schedule.TryGetValue(change.Culture, out var s))
|
||||
{
|
||||
var removed = s.Remove(change.Date);
|
||||
if (removed)
|
||||
{
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, change));
|
||||
if (s.Count == 0)
|
||||
_schedule.Remove(change.Culture);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all of the scheduled change type for invariant content
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="changeDate">If specified, will clear all entries with dates less than or equal to the value</param>
|
||||
public void Clear(ContentScheduleAction action, DateTime? changeDate = null)
|
||||
{
|
||||
Clear(string.Empty, action, changeDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all of the scheduled change type for the culture
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="date">If specified, will clear all entries with dates less than or equal to the value</param>
|
||||
public void Clear(string culture, ContentScheduleAction action, DateTime? date = null)
|
||||
{
|
||||
if (!_schedule.TryGetValue(culture, out var schedules))
|
||||
return;
|
||||
|
||||
var removes = schedules.Where(x => x.Value.Action == action && (!date.HasValue || x.Value.Date <= date.Value)).ToList();
|
||||
|
||||
foreach (var remove in removes)
|
||||
{
|
||||
var removed = schedules.Remove(remove.Value.Date);
|
||||
if (!removed)
|
||||
continue;
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, remove.Value));
|
||||
}
|
||||
|
||||
if (schedules.Count == 0)
|
||||
_schedule.Remove(culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all pending schedules based on the date and type provided
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="date"></param>
|
||||
/// <returns></returns>
|
||||
public IReadOnlyList<ContentSchedule> GetPending(ContentScheduleAction action, DateTime date)
|
||||
{
|
||||
return _schedule.Values.SelectMany(x => x.Values).Where(x => x.Date <= date).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the schedule for invariant content
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<ContentSchedule> GetSchedule(ContentScheduleAction? action = null)
|
||||
{
|
||||
return GetSchedule(string.Empty, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the schedule for a culture
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<ContentSchedule> GetSchedule(string culture, ContentScheduleAction? action = null)
|
||||
{
|
||||
if (_schedule.TryGetValue(culture, out var changes))
|
||||
return action == null ? changes.Values : changes.Values.Where(x => x.Action == action.Value);
|
||||
return Enumerable.Empty<ContentSchedule>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all schedules registered
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IReadOnlyList<ContentSchedule> FullSchedule => _schedule.SelectMany(x => x.Value.Values).ToList();
|
||||
|
||||
public object DeepClone()
|
||||
{
|
||||
var clone = new ContentScheduleCollection();
|
||||
foreach(var cultureSched in _schedule)
|
||||
{
|
||||
var list = new SortedList<DateTime, ContentSchedule>();
|
||||
foreach (var schedEntry in cultureSched.Value)
|
||||
list.Add(schedEntry.Key, (ContentSchedule)schedEntry.Value.DeepClone());
|
||||
clone._schedule[cultureSched.Key] = list;
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ContentScheduleCollection other && Equals(other);
|
||||
|
||||
public bool Equals(ContentScheduleCollection other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
var thisSched = _schedule;
|
||||
var thatSched = other._schedule;
|
||||
|
||||
if (thisSched.Count != thatSched.Count)
|
||||
return false;
|
||||
|
||||
foreach (var (culture, thisList) in thisSched)
|
||||
{
|
||||
// if culture is missing, or actions differ, false
|
||||
if (!thatSched.TryGetValue(culture, out var thatList) || !thatList.SequenceEqual(thisList))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,42 @@ using System.Runtime.Serialization;
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum for the various statuses a Content object can have
|
||||
/// Describes the states of a document, with regard to (schedule) publishing.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract]
|
||||
public enum ContentStatus
|
||||
{
|
||||
// typical flow:
|
||||
// Unpublished (add release date)-> AwaitingRelease (release)-> Published (expire)-> Expired
|
||||
|
||||
/// <summary>
|
||||
/// The document is not trashed, and not published.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
Unpublished,
|
||||
|
||||
/// <summary>
|
||||
/// The document is published.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
Published,
|
||||
|
||||
/// <summary>
|
||||
/// The document is not trashed, not published, after being unpublished by a scheduled action.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
Expired,
|
||||
|
||||
/// <summary>
|
||||
/// The document is trashed.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
Trashed,
|
||||
|
||||
/// <summary>
|
||||
/// The document is not trashed, not published, and pending publication by a scheduled action.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
AwaitingRelease
|
||||
}
|
||||
|
||||
@@ -92,8 +92,8 @@ namespace Umbraco.Core.Models
|
||||
public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.AllowedAsRoot);
|
||||
public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.IsContainer);
|
||||
public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<ContentTypeSort>>(x => x.AllowedContentTypes);
|
||||
public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, PropertyGroupCollection>(x => x.PropertyGroups);
|
||||
public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<PropertyType>>(x => x.PropertyTypes);
|
||||
public readonly PropertyInfo PropertyGroupsSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, PropertyGroupCollection>(x => x.PropertyGroups);
|
||||
public readonly PropertyInfo PropertyTypesSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<PropertyType>>(x => x.PropertyTypes);
|
||||
public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.HasPropertyTypeBeenRemoved);
|
||||
public readonly PropertyInfo VaryBy = ExpressionHelper.GetPropertyInfo<ContentTypeBase, ContentVariation>(x => x.Variations);
|
||||
|
||||
@@ -106,12 +106,12 @@ namespace Umbraco.Core.Models
|
||||
|
||||
protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector);
|
||||
OnPropertyChanged(Ps.Value.PropertyGroupsSelector);
|
||||
}
|
||||
|
||||
protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
|
||||
OnPropertyChanged(Ps.Value.PropertyTypesSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -263,6 +263,8 @@ namespace Umbraco.Core.Models
|
||||
get => _noGroupPropertyTypes;
|
||||
set
|
||||
{
|
||||
if (_noGroupPropertyTypes != null)
|
||||
_noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged;
|
||||
_noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing, value);
|
||||
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
|
||||
PropertyTypesChanged(_noGroupPropertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
@@ -376,7 +378,7 @@ namespace Umbraco.Core.Models
|
||||
if (!HasPropertyTypeBeenRemoved)
|
||||
{
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
|
||||
OnPropertyChanged(Ps.Value.PropertyTypesSelector);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -388,7 +390,7 @@ namespace Umbraco.Core.Models
|
||||
if (!HasPropertyTypeBeenRemoved)
|
||||
{
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
|
||||
OnPropertyChanged(Ps.Value.PropertyTypesSelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,7 +414,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
// actually remove the group
|
||||
PropertyGroups.RemoveItem(propertyGroupName);
|
||||
OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector);
|
||||
OnPropertyChanged(Ps.Value.PropertyGroupsSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,8 @@ using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
//TODO: Make a property value converter for this!
|
||||
|
||||
/// <summary>
|
||||
/// A model representing the value saved for the grid
|
||||
/// </summary>
|
||||
@@ -19,7 +21,7 @@ namespace Umbraco.Core.Models
|
||||
public class GridSection
|
||||
{
|
||||
[JsonProperty("grid")]
|
||||
public string Grid { get; set; }
|
||||
public string Grid { get; set; } //fixme: what is this?
|
||||
|
||||
[JsonProperty("rows")]
|
||||
public IEnumerable<GridRow> Rows { get; set; }
|
||||
@@ -46,7 +48,7 @@ namespace Umbraco.Core.Models
|
||||
public class GridArea
|
||||
{
|
||||
[JsonProperty("grid")]
|
||||
public string Grid { get; set; }
|
||||
public string Grid { get; set; } //fixme: what is this?
|
||||
|
||||
[JsonProperty("controls")]
|
||||
public IEnumerable<GridControl> Controls { get; set; }
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a document.
|
||||
/// </summary>
|
||||
@@ -11,6 +12,11 @@ namespace Umbraco.Core.Models
|
||||
/// </remarks>
|
||||
public interface IContent : IContentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the content schedule
|
||||
/// </summary>
|
||||
ContentScheduleCollection ContentSchedule { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the template used to render the content.
|
||||
/// </summary>
|
||||
@@ -60,26 +66,11 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
DateTime? PublishDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time the content item should be published.
|
||||
/// </summary>
|
||||
DateTime? ReleaseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time the content should be unpublished.
|
||||
/// </summary>
|
||||
DateTime? ExpireDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content type of this content.
|
||||
/// </summary>
|
||||
IContentType ContentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of the content.
|
||||
/// </summary>
|
||||
ContentStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a culture is published.
|
||||
/// </summary>
|
||||
@@ -89,6 +80,7 @@ namespace Umbraco.Core.Models
|
||||
/// whenever values for this culture are unpublished.</para>
|
||||
/// <para>A culture becomes published as soon as PublishCulture has been invoked,
|
||||
/// even though the document might now have been saved yet (and can have no identity).</para>
|
||||
/// <para>Does not support the '*' wildcard (returns false).</para>
|
||||
/// </remarks>
|
||||
bool IsCulturePublished(string culture);
|
||||
|
||||
@@ -112,6 +104,7 @@ namespace Umbraco.Core.Models
|
||||
/// <para>A culture is edited when it is available, and not published or published but
|
||||
/// with changes.</para>
|
||||
/// <para>A culture can be edited even though the document might now have been saved yet (and can have no identity).</para>
|
||||
/// <para>Does not support the '*' wildcard (returns false).</para>
|
||||
/// </remarks>
|
||||
bool IsCultureEdited(string culture);
|
||||
|
||||
@@ -172,7 +165,7 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether the culture can be published.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Fails if properties don't pass variant validtion rules.</para>
|
||||
/// <para>Fails if properties don't pass variant validation rules.</para>
|
||||
/// <para>Publishing must be finalized via the content service SavePublishing method.</para>
|
||||
/// </remarks>
|
||||
bool PublishCulture(string culture = "*");
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Umbraco.Core.Models
|
||||
/// <para>Returns <c>false</c> for the invariant culture, in order to be consistent
|
||||
/// with <seealso cref="AvailableCultures"/>, even though the invariant culture is
|
||||
/// always available.</para>
|
||||
/// <para>Does not support the '*' wildcard (returns false).</para>
|
||||
/// </remarks>
|
||||
bool IsCultureAvailable(string culture);
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -48,7 +51,57 @@ namespace Umbraco.Core.Models
|
||||
[DataMember]
|
||||
public string CultureName
|
||||
{
|
||||
get => _cultureName ?? CultureInfo.GetCultureInfo(IsoCode).DisplayName;
|
||||
// CultureInfo.DisplayName is the name in the installed .NET language
|
||||
// .NativeName is the name in culture info's language
|
||||
// .EnglishName is the name in English
|
||||
//
|
||||
// there is no easy way to get the name in a specified culture (which would need to be installed on the server)
|
||||
// this works:
|
||||
// var rm = new ResourceManager("mscorlib", typeof(int).Assembly);
|
||||
// var name = rm.GetString("Globalization.ci_" + culture.Name, displayCulture);
|
||||
// but can we rely on it?
|
||||
//
|
||||
// and... DisplayName is captured and cached in culture infos returned by GetCultureInfo(), using
|
||||
// the value for the current thread culture at the moment it is first retrieved - whereas creating
|
||||
// a new CultureInfo() creates a new instance, which _then_ can get DisplayName again in a different
|
||||
// culture
|
||||
//
|
||||
// I assume that, on a site, all language names should be in the SAME language, in DB,
|
||||
// and that would be the umbracoDefaultUILanguage (app setting) - BUT if by accident
|
||||
// ANY culture has been retrieved with another current thread culture - it's now corrupt
|
||||
//
|
||||
// so, the logic below ensures that the name always end up being the correct name
|
||||
// see also LanguageController.GetAllCultures which is doing the same
|
||||
//
|
||||
// all this, including the ugly settings injection, because se store language names in db,
|
||||
// otherwise it would be ok to simply return new CultureInfo(IsoCode).DisplayName to get the name
|
||||
// in whatever culture is current - we should not do it, see task #3623
|
||||
//
|
||||
// but then, some tests that compare audit strings (for culture names) would need to be fixed
|
||||
|
||||
get
|
||||
{
|
||||
if (_cultureName != null) return _cultureName;
|
||||
|
||||
// capture
|
||||
var threadUiCulture = Thread.CurrentThread.CurrentUICulture;
|
||||
|
||||
try
|
||||
{
|
||||
var globalSettings = (IGlobalSettings) Composing.Current.Container.GetInstance(typeof(IGlobalSettings));
|
||||
var defaultUiCulture = CultureInfo.GetCultureInfo(globalSettings.DefaultUILanguage);
|
||||
Thread.CurrentThread.CurrentUICulture = defaultUiCulture;
|
||||
|
||||
// get name - new-ing an instance to get proper display name
|
||||
return new CultureInfo(IsoCode).DisplayName;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// restore
|
||||
Thread.CurrentThread.CurrentUICulture = threadUiCulture;
|
||||
}
|
||||
}
|
||||
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
if (pageSize > 0)
|
||||
{
|
||||
TotalPages = (long)Math.Ceiling(totalItems / (Decimal)pageSize);
|
||||
TotalPages = (long)Math.Ceiling(totalItems / (decimal)pageSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -35,12 +35,12 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<PropertyGroup, string>(x => x.Name);
|
||||
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<PropertyGroup, int>(x => x.SortOrder);
|
||||
public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo<PropertyGroup, PropertyTypeCollection>(x => x.PropertyTypes);
|
||||
public readonly PropertyInfo PropertyTypes = ExpressionHelper.GetPropertyInfo<PropertyGroup, PropertyTypeCollection>(x => x.PropertyTypes);
|
||||
}
|
||||
|
||||
private void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
|
||||
OnPropertyChanged(Ps.Value.PropertyTypes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -76,6 +76,8 @@ namespace Umbraco.Core.Models
|
||||
get => _propertyTypes;
|
||||
set
|
||||
{
|
||||
if (_propertyTypes != null)
|
||||
_propertyTypes.CollectionChanged -= PropertyTypesChanged;
|
||||
_propertyTypes = value;
|
||||
|
||||
// since we're adding this collection to this group,
|
||||
@@ -83,6 +85,7 @@ namespace Umbraco.Core.Models
|
||||
foreach (var propertyType in _propertyTypes)
|
||||
propertyType.PropertyGroupId = new Lazy<int>(() => Id);
|
||||
|
||||
OnPropertyChanged(Ps.Value.PropertyTypes);
|
||||
_propertyTypes.CollectionChanged += PropertyTypesChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace Umbraco.Core.Models
|
||||
|
||||
public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent noAccessNode, IEnumerable<PublicAccessRule> ruleCollection)
|
||||
{
|
||||
if (protectedNode == null) throw new ArgumentNullException(nameof(protectedNode));
|
||||
if (loginNode == null) throw new ArgumentNullException(nameof(loginNode));
|
||||
if (noAccessNode == null) throw new ArgumentNullException(nameof(noAccessNode));
|
||||
|
||||
LoginNodeId = loginNode.Id;
|
||||
NoAccessNodeId = noAccessNode.Id;
|
||||
_protectedNodeId = protectedNode.Id;
|
||||
|
||||
@@ -3,12 +3,14 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
@@ -54,8 +56,11 @@ namespace Umbraco.Core.Models
|
||||
/// </returns>
|
||||
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")
|
||||
// If FIPS is required, never check the Gravatar service as it only supports MD5 hashing.
|
||||
// Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception
|
||||
// and the website will not run.
|
||||
// Also, 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" || CryptoConfig.AllowOnlyFipsAlgorithms)
|
||||
{
|
||||
return new string[0];
|
||||
}
|
||||
@@ -146,68 +151,46 @@ namespace Umbraco.Core.Models
|
||||
|
||||
internal static bool HasContentRootAccess(this IUser user, IEntityService entityService)
|
||||
{
|
||||
return HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
|
||||
return ContentPermissionsHelper.HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
|
||||
}
|
||||
|
||||
internal static bool HasContentBinAccess(this IUser user, IEntityService entityService)
|
||||
{
|
||||
return HasPathAccess(Constants.System.RecycleBinContent.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
|
||||
return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinContent.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
|
||||
}
|
||||
|
||||
internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService)
|
||||
{
|
||||
return HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
|
||||
return ContentPermissionsHelper.HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
|
||||
}
|
||||
|
||||
internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService)
|
||||
{
|
||||
return HasPathAccess(Constants.System.RecycleBinMedia.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
|
||||
return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinMedia.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
|
||||
}
|
||||
|
||||
internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService)
|
||||
{
|
||||
return HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
return ContentPermissionsHelper.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
|
||||
}
|
||||
|
||||
internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService)
|
||||
{
|
||||
return HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
|
||||
if (media == null) throw new ArgumentNullException(nameof(media));
|
||||
return ContentPermissionsHelper.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
|
||||
}
|
||||
|
||||
internal static bool HasPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId)
|
||||
internal static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService)
|
||||
{
|
||||
switch (recycleBinId)
|
||||
{
|
||||
case Constants.System.RecycleBinMedia:
|
||||
return HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), recycleBinId);
|
||||
case Constants.System.RecycleBinContent:
|
||||
return HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), recycleBinId);
|
||||
default:
|
||||
throw new NotSupportedException("Path access is only determined on content or media");
|
||||
}
|
||||
if (entity == null) throw new ArgumentNullException(nameof(entity));
|
||||
return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
|
||||
}
|
||||
|
||||
internal static bool HasPathAccess(string path, int[] startNodeIds, int recycleBinId)
|
||||
internal static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(path));
|
||||
|
||||
// check for no access
|
||||
if (startNodeIds.Length == 0)
|
||||
return false;
|
||||
|
||||
// check for root access
|
||||
if (startNodeIds.Contains(Constants.System.Root))
|
||||
return true;
|
||||
|
||||
var formattedPath = string.Concat(",", path, ",");
|
||||
|
||||
// only users with root access have access to the recycle bin,
|
||||
// if the above check didn't pass then access is denied
|
||||
if (formattedPath.Contains(string.Concat(",", recycleBinId, ",")))
|
||||
return false;
|
||||
|
||||
// check for a start node in the path
|
||||
return startNodeIds.Any(x => formattedPath.Contains(string.Concat(",", x, ",")));
|
||||
if (entity == null) throw new ArgumentNullException(nameof(entity));
|
||||
return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
|
||||
}
|
||||
|
||||
internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess)
|
||||
@@ -215,58 +198,14 @@ namespace Umbraco.Core.Models
|
||||
switch (recycleBinId)
|
||||
{
|
||||
case Constants.System.RecycleBinMedia:
|
||||
return IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess);
|
||||
return ContentPermissionsHelper.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);
|
||||
return ContentPermissionsHelper.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.", 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this user has access to view sensitive data
|
||||
/// </summary>
|
||||
|
||||
@@ -724,8 +724,8 @@ namespace Umbraco.Core
|
||||
{
|
||||
return typeConverter;
|
||||
}
|
||||
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(target);
|
||||
|
||||
var converter = TypeDescriptor.GetConverter(target);
|
||||
if (converter.CanConvertFrom(source))
|
||||
{
|
||||
return DestinationTypeConverterCache[key] = converter;
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Umbraco.Core
|
||||
public const string DocumentCultureVariation = TableNamePrefix + "DocumentCultureVariation";
|
||||
public const string DocumentVersion = TableNamePrefix + "DocumentVersion";
|
||||
public const string MediaVersion = TableNamePrefix + "MediaVersion";
|
||||
public const string ContentSchedule = TableNamePrefix + "ContentSchedule";
|
||||
|
||||
public const string PropertyType = /*TableNamePrefix*/ "cms" + "PropertyType";
|
||||
public const string PropertyTypeGroup = /*TableNamePrefix*/ "cms" + "PropertyTypeGroup";
|
||||
|
||||
@@ -159,9 +159,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
Name = indexName,
|
||||
IndexType = attribute.IndexType,
|
||||
ColumnName = columnName,
|
||||
TableName = tableName,
|
||||
IsClustered = attribute.IndexType == IndexTypes.Clustered,
|
||||
IsUnique = attribute.IndexType == IndexTypes.UniqueNonClustered
|
||||
TableName = tableName,
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(attribute.ForColumns) == false)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
@@ -14,8 +15,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
public virtual string SchemaName { get; set; }
|
||||
public virtual string TableName { get; set; }
|
||||
public virtual string ColumnName { get; set; }
|
||||
public virtual bool IsUnique { get; set; }
|
||||
public bool IsClustered { get; set; }
|
||||
|
||||
public virtual ICollection<IndexColumnDefinition> Columns { get; set; }
|
||||
public IndexTypes IndexType { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Dtos
|
||||
{
|
||||
[TableName(TableName)]
|
||||
[PrimaryKey("id", AutoIncrement = false)]
|
||||
[ExplicitColumns]
|
||||
internal class ContentScheduleDto
|
||||
{
|
||||
public const string TableName = Constants.DatabaseSchema.Tables.ContentSchedule;
|
||||
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(AutoIncrement = false)]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Column("nodeId")]
|
||||
[ForeignKey(typeof(ContentDto))]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("languageId")]
|
||||
[ForeignKey(typeof(LanguageDto))]
|
||||
[NullSetting(NullSetting = NullSettings.Null)] // can be invariant
|
||||
public int? LanguageId { get; set; }
|
||||
|
||||
[Column("date")]
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
[Column("action")]
|
||||
public string Action { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using NPoco;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Dtos
|
||||
{
|
||||
|
||||
[TableName(TableName)]
|
||||
[PrimaryKey("nodeId", AutoIncrement = false)]
|
||||
[ExplicitColumns]
|
||||
@@ -23,14 +23,6 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
[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; }
|
||||
|
||||
@@ -22,10 +22,6 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
[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; }
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
@@ -45,8 +48,6 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
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)
|
||||
@@ -142,7 +143,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
content.CreateDate = nodeDto.CreateDate;
|
||||
content.UpdateDate = contentVersionDto.VersionDate;
|
||||
|
||||
content.ProviderUserKey = content.Key; // fixme explain
|
||||
content.ProviderUserKey = content.Key; // The `ProviderUserKey` is a membership provider thing
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
content.ResetDirtyProperties(false);
|
||||
@@ -155,7 +156,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buils a dto from an IContent item.
|
||||
/// Builds a dto from an IContent item.
|
||||
/// </summary>
|
||||
public static DocumentDto BuildDto(IContent entity, Guid objectType)
|
||||
{
|
||||
@@ -165,9 +166,6 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
NodeId = entity.Id,
|
||||
Published = entity.Published,
|
||||
ReleaseDate = entity.ReleaseDate,
|
||||
ExpiresDate = entity.ExpireDate,
|
||||
|
||||
ContentDto = contentDto,
|
||||
DocumentVersionDto = BuildDocumentVersionDto(entity, contentDto)
|
||||
};
|
||||
@@ -175,6 +173,19 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
return dto;
|
||||
}
|
||||
|
||||
public static IEnumerable<(ContentSchedule Model, ContentScheduleDto Dto)> BuildScheduleDto(IContent entity, ILanguageRepository languageRepository)
|
||||
{
|
||||
return entity.ContentSchedule.FullSchedule.Select(x =>
|
||||
(x, new ContentScheduleDto
|
||||
{
|
||||
Action = x.Action.ToString(),
|
||||
Date = x.Date,
|
||||
NodeId = entity.Id,
|
||||
LanguageId = languageRepository.GetIdByIsoCode(x.Culture, false),
|
||||
Id = x.Id
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buils a dto from an IMedia item.
|
||||
/// </summary>
|
||||
@@ -302,6 +313,9 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
// more dark magic ;-(
|
||||
internal static bool TryMatch(string text, out string path)
|
||||
{
|
||||
//fixme: In v8 we should allow exposing this via the property editor in a much nicer way so that the property editor
|
||||
// can tell us directly what any URL is for a given property if it contains an asset
|
||||
|
||||
path = null;
|
||||
if (string.IsNullOrWhiteSpace(text)) return false;
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
EditorAlias = entity.EditorAlias,
|
||||
NodeId = entity.Id,
|
||||
DbType = entity.DatabaseType.ToString(),
|
||||
Configuration = entity.Configuration == null ? null : JsonConvert.SerializeObject(entity.Configuration, ConfigurationEditor.ConfigurationJsonSettings),
|
||||
Configuration = ConfigurationEditor.ToDatabase(entity.Configuration),
|
||||
NodeDto = BuildNodeDto(entity)
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
var dto = new TemplateDto
|
||||
{
|
||||
Alias = entity.Alias,
|
||||
Design = entity.Content ?? string.Empty,
|
||||
NodeDto = BuildNodeDto(entity, nodeObjectTypeId)
|
||||
};
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ namespace Umbraco.Core.Persistence.Mappers
|
||||
CacheMap<Content, ContentDto>(src => src.ContentTypeId, dto => dto.ContentTypeId);
|
||||
|
||||
CacheMap<Content, ContentVersionDto>(src => src.UpdateDate, dto => dto.VersionDate);
|
||||
CacheMap<Content, DocumentDto>(src => src.ExpireDate, dto => dto.ExpiresDate);
|
||||
CacheMap<Content, DocumentDto>(src => src.ReleaseDate, dto => dto.ReleaseDate);
|
||||
CacheMap<Content, DocumentDto>(src => src.Published, dto => dto.Published);
|
||||
|
||||
//CacheMap<Content, DocumentDto>(src => src.Name, dto => dto.Alias);
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace Umbraco.Core.Persistence.Mappers
|
||||
CacheMap<Template, NodeDto>(src => src.MasterTemplateId, dto => dto.ParentId);
|
||||
CacheMap<Template, NodeDto>(src => src.Key, dto => dto.UniqueId);
|
||||
CacheMap<Template, TemplateDto>(src => src.Alias, dto => dto.Alias);
|
||||
CacheMap<Template, TemplateDto>(src => src.Content, dto => dto.Design);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
/// <summary>
|
||||
/// Gets or sets the SQL syntax provider for the current database.
|
||||
/// </summary>
|
||||
protected ISqlSyntaxProvider SqlSyntax { get; private set; }
|
||||
protected ISqlSyntaxProvider SqlSyntax { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of SQL parameters.
|
||||
@@ -56,6 +56,8 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
/// <remarks>Also populates the SQL parameters.</remarks>
|
||||
public virtual string Visit(Expression expression)
|
||||
{
|
||||
if (expression == null) return string.Empty;
|
||||
|
||||
// if the expression is a CachedExpression,
|
||||
// visit the inner expression if not already visited
|
||||
var cachedExpression = expression as CachedExpression;
|
||||
@@ -65,8 +67,6 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
expression = cachedExpression.InnerExpression;
|
||||
}
|
||||
|
||||
if (expression == null) return string.Empty;
|
||||
|
||||
string result;
|
||||
|
||||
switch (expression.NodeType)
|
||||
@@ -135,35 +135,28 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
|
||||
// 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;
|
||||
if (cachedExpression == null)
|
||||
return result;
|
||||
if (!cachedExpression.Visited)
|
||||
cachedExpression.VisitResult = result;
|
||||
return cachedExpression.VisitResult;
|
||||
}
|
||||
|
||||
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)
|
||||
if (lambda.Body.NodeType == ExpressionType.MemberAccess &&
|
||||
lambda.Body is MemberExpression memberExpression && memberExpression.Expression != null)
|
||||
{
|
||||
//This deals with members that are boolean (i.e. x => IsTrashed )
|
||||
var r = VisitMemberAccess(m);
|
||||
var result = VisitMemberAccess(memberExpression);
|
||||
|
||||
SqlParameters.Add(true);
|
||||
|
||||
return Visited ? string.Empty : string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
|
||||
return Visited ? string.Empty : $"{result} = @{SqlParameters.Count - 1}";
|
||||
}
|
||||
|
||||
}
|
||||
return Visit(lambda.Body);
|
||||
}
|
||||
|
||||
@@ -248,21 +241,10 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
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;
|
||||
return Visited ? string.Empty : $"{operand}({left},{right})";
|
||||
|
||||
default:
|
||||
//don't execute if compiled
|
||||
if (Visited == false)
|
||||
{
|
||||
return string.Concat("(", left, " ", operand, " ", right, ")");
|
||||
}
|
||||
//already compiled, return
|
||||
return string.Empty;
|
||||
return Visited ? string.Empty : $"({left} {operand} {right})";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,10 +266,10 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
return list;
|
||||
}
|
||||
|
||||
protected virtual string VisitNew(NewExpression nex)
|
||||
protected virtual string VisitNew(NewExpression newExpression)
|
||||
{
|
||||
// TODO : check !
|
||||
var member = Expression.Convert(nex, typeof(object));
|
||||
var member = Expression.Convert(newExpression, typeof(object));
|
||||
var lambda = Expression.Lambda<Func<object>>(member);
|
||||
try
|
||||
{
|
||||
@@ -295,20 +277,16 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
var o = getter();
|
||||
|
||||
SqlParameters.Add(o);
|
||||
|
||||
return Visited ? string.Empty : $"@{SqlParameters.Count - 1}";
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
if (Visited) return string.Empty;
|
||||
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();
|
||||
var exprs = VisitExpressionList(newExpression.Arguments);
|
||||
return string.Join(",", exprs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +301,7 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
return "null";
|
||||
|
||||
SqlParameters.Add(c.Value);
|
||||
|
||||
return Visited ? string.Empty : $"@{SqlParameters.Count - 1}";
|
||||
}
|
||||
|
||||
@@ -375,27 +354,11 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
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;
|
||||
return Visited ? string.Empty : string.Join(",", exprs);
|
||||
}
|
||||
|
||||
protected virtual List<object> VisitNewArrayFromExpressionList(NewArrayExpression na)
|
||||
{
|
||||
var exprs = VisitExpressionList(na.Expressions);
|
||||
return exprs;
|
||||
}
|
||||
=> VisitExpressionList(na.Expressions);
|
||||
|
||||
protected virtual string BindOperant(ExpressionType e)
|
||||
{
|
||||
@@ -436,50 +399,61 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
|
||||
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.
|
||||
// m.Object is the expression that represent the instance for instance method class, or null for static method calls
|
||||
// m.Arguments is the collection of expressions that represent arguments of the called method
|
||||
// m.MethodInfo is the method info for the method to be called
|
||||
|
||||
var objectForMethod = m.Object ?? m.Arguments[0];
|
||||
var visitedObjectForMethod = Visit(objectForMethod);
|
||||
// assume that static methods are extension methods (probably not ok)
|
||||
// and then, the method object is its first argument - get "safe" object
|
||||
var methodObject = m.Object ?? m.Arguments[0];
|
||||
var visitedMethodObject = Visit(methodObject);
|
||||
// and then, "safe" arguments are what would come after the first arg
|
||||
var methodArgs = m.Object == null
|
||||
? m.Arguments.Skip(1).ToArray()
|
||||
: m.Arguments.ToArray();
|
||||
? new ReadOnlyCollection<Expression>(m.Arguments.Skip(1).ToList())
|
||||
: m.Arguments;
|
||||
|
||||
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;
|
||||
SqlParameters.Add(methodObject.ToString());
|
||||
return Visited ? string.Empty : $"@{SqlParameters.Count - 1}";
|
||||
|
||||
case "ToUpper":
|
||||
//don't execute if compiled
|
||||
if (Visited == false)
|
||||
return string.Format("upper({0})", visitedObjectForMethod);
|
||||
//already compiled, return
|
||||
return string.Empty;
|
||||
return Visited ? string.Empty : $"upper({visitedMethodObject})";
|
||||
|
||||
case "ToLower":
|
||||
//don't execute if compiled
|
||||
if (Visited == false)
|
||||
return string.Format("lower({0})", visitedObjectForMethod);
|
||||
//already compiled, return
|
||||
return string.Empty;
|
||||
return Visited ? string.Empty : $"lower({visitedMethodObject})";
|
||||
|
||||
case "Contains":
|
||||
// for 'Contains', it can either be the string.Contains(string) method, or a collection Contains
|
||||
// method, which would then need to become a SQL IN clause - but beware that string is
|
||||
// an enumerable of char, and string.Contains(char) is an extension method - but NOT an SQL IN
|
||||
|
||||
var isCollectionContains =
|
||||
(
|
||||
m.Object == null && // static (extension?) method
|
||||
m.Arguments.Count == 2 && // with two args
|
||||
m.Arguments[0].Type != typeof(string) && // but not for string
|
||||
TypeHelper.IsTypeAssignableFrom<IEnumerable>(m.Arguments[0].Type) && // first arg being an enumerable
|
||||
m.Arguments[1].NodeType == ExpressionType.MemberAccess // second arg being a member access
|
||||
) ||
|
||||
(
|
||||
m.Object != null && // instance method
|
||||
TypeHelper.IsTypeAssignableFrom<IEnumerable>(m.Object.Type) && // of an enumerable
|
||||
m.Object.Type != typeof(string) && // but not for string
|
||||
m.Arguments.Count == 1 && // with 1 arg
|
||||
m.Arguments[0].NodeType == ExpressionType.MemberAccess // arg being a member access
|
||||
);
|
||||
|
||||
if (isCollectionContains)
|
||||
goto case "SqlIn";
|
||||
else
|
||||
goto case "Contains**String";
|
||||
|
||||
case "SqlWildcard":
|
||||
case "StartsWith":
|
||||
case "EndsWith":
|
||||
case "Contains":
|
||||
case "Contains**String": // see "Contains" above
|
||||
case "Equals":
|
||||
case "SqlStartsWith":
|
||||
case "SqlEndsWith":
|
||||
@@ -490,18 +464,6 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
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<IEnumerable>(m.Arguments[0].Type))
|
||||
{
|
||||
goto case "SqlIn";
|
||||
}
|
||||
|
||||
string compareValue;
|
||||
|
||||
if (methodArgs[0].NodeType != ExpressionType.Constant)
|
||||
@@ -526,7 +488,7 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
|
||||
//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)
|
||||
if (methodArgs.Count > 1)
|
||||
{
|
||||
var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType));
|
||||
if (colTypeArg != null)
|
||||
@@ -535,7 +497,7 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
}
|
||||
}
|
||||
|
||||
return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType);
|
||||
return HandleStringComparison(visitedMethodObject, compareValue, m.Method.Name, colType);
|
||||
|
||||
case "Replace":
|
||||
string searchValue;
|
||||
@@ -581,14 +543,10 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
}
|
||||
|
||||
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;
|
||||
return Visited ? string.Empty : $"replace({visitedMethodObject}, @{SqlParameters.Count - 2}, @{SqlParameters.Count - 1})";
|
||||
|
||||
//case "Substring":
|
||||
// var startIndex = Int32.Parse(args[0].ToString()) + 1;
|
||||
@@ -624,30 +582,33 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
|
||||
case "SqlIn":
|
||||
|
||||
if (m.Object == null && methodArgs.Length == 1 && methodArgs[0].NodeType == ExpressionType.MemberAccess)
|
||||
if (methodArgs.Count != 1 || methodArgs[0].NodeType != ExpressionType.MemberAccess)
|
||||
throw new NotSupportedException("SqlIn must contain the member being accessed.");
|
||||
|
||||
var memberAccess = VisitMemberAccess((MemberExpression) methodArgs[0]);
|
||||
|
||||
var inMember = Expression.Convert(methodObject, typeof(object));
|
||||
var inLambda = Expression.Lambda<Func<object>>(inMember);
|
||||
var inGetter = inLambda.Compile();
|
||||
|
||||
var inArgs = (IEnumerable) inGetter();
|
||||
|
||||
var inBuilder = new StringBuilder();
|
||||
var inFirst = true;
|
||||
|
||||
inBuilder.Append(memberAccess);
|
||||
inBuilder.Append(" IN (");
|
||||
|
||||
foreach (var e in inArgs)
|
||||
{
|
||||
var memberAccess = VisitMemberAccess((MemberExpression) methodArgs[0]);
|
||||
|
||||
var member = Expression.Convert(m.Arguments[0], typeof(object));
|
||||
var lambda = Expression.Lambda<Func<object>>(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);
|
||||
SqlParameters.Add(e);
|
||||
if (inFirst) inFirst = false; else inBuilder.Append(",");
|
||||
inBuilder.Append("@");
|
||||
inBuilder.Append(SqlParameters.Count - 1);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("SqlIn must contain the member being accessed");
|
||||
inBuilder.Append(")");
|
||||
return inBuilder.ToString();
|
||||
|
||||
//case "Desc":
|
||||
// return string.Format("{0} DESC", r);
|
||||
@@ -706,19 +667,13 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
}
|
||||
|
||||
public virtual string GetQuotedTableName(string tableName)
|
||||
{
|
||||
return Visited ? tableName : string.Format("\"{0}\"", tableName);
|
||||
}
|
||||
=> GetQuotedName(tableName);
|
||||
|
||||
public virtual string GetQuotedColumnName(string columnName)
|
||||
{
|
||||
return Visited ? columnName : string.Format("\"{0}\"", columnName);
|
||||
}
|
||||
=> GetQuotedName(columnName);
|
||||
|
||||
public virtual string GetQuotedName(string name)
|
||||
{
|
||||
return Visited ? name : string.Format("\"{0}\"", name);
|
||||
}
|
||||
=> Visited ? name : "\"" + name + "\"";
|
||||
|
||||
protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType)
|
||||
{
|
||||
@@ -726,115 +681,38 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
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;
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
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);
|
||||
SqlParameters.Add(RemoveQuote(val));
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
case "StartsWith":
|
||||
case "InvariantStartsWith":
|
||||
case "SqlStartsWith":
|
||||
//recurse
|
||||
return HandleStringComparison(col, val, "StartsWith", columnType);
|
||||
SqlParameters.Add(RemoveQuote(val) + SqlSyntax.GetWildcardPlaceholder());
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
case "EndsWith":
|
||||
case "InvariantEndsWith":
|
||||
case "SqlEndsWith":
|
||||
//recurse
|
||||
return HandleStringComparison(col, val, "EndsWith", columnType);
|
||||
SqlParameters.Add(SqlSyntax.GetWildcardPlaceholder() + RemoveQuote(val));
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
case "Contains":
|
||||
case "InvariantContains":
|
||||
case "SqlContains":
|
||||
//recurse
|
||||
return HandleStringComparison(col, val, "Contains", columnType);
|
||||
var wildcardPlaceholder = SqlSyntax.GetWildcardPlaceholder();
|
||||
SqlParameters.Add(wildcardPlaceholder + RemoveQuote(val) + wildcardPlaceholder);
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("verb");
|
||||
throw new ArgumentOutOfRangeException(nameof(verb));
|
||||
}
|
||||
}
|
||||
|
||||
//public virtual string GetQuotedValue(object value, Type fieldType, Func<object, string> escapeCallback = null, Func<Type, bool> 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
|
||||
@@ -842,34 +720,14 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
: 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;
|
||||
if (exp.IsNullOrWhiteSpace()) return exp;
|
||||
|
||||
var c = exp[0];
|
||||
return (c == '"' || c == '`' || c == '\'') && exp[exp.Length - 1] == c
|
||||
? exp.Substring(1, exp.Length - 2)
|
||||
: 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;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
interface IDocumentBlueprintRepository : IDocumentRepository
|
||||
public interface IDocumentBlueprintRepository : IDocumentRepository
|
||||
{ }
|
||||
}
|
||||
|
||||
@@ -7,6 +7,29 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IDocumentRepository : IContentRepository<int, IContent>, IReadRepository<Guid, IContent>
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears the publishing schedule for all entries having an a date before (lower than, or equal to) a specified date.
|
||||
/// </summary>
|
||||
void ClearSchedule(DateTime date);
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="IContent"/> objects having an expiration date before (lower than, or equal to) a specified date.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The content returned from this method may be culture variant, in which case the resulting <see cref="IContent.ContentSchedule"/> should be queried
|
||||
/// for which culture(s) have been scheduled.
|
||||
/// </remarks>
|
||||
IEnumerable<IContent> GetContentForExpiration(DateTime date);
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="IContent"/> objects having a release date before (lower than, or equal to) a specified date.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The content returned from this method may be culture variant, in which case the resulting <see cref="IContent.ContentSchedule"/> should be queried
|
||||
/// for which culture(s) have been scheduled.
|
||||
/// </remarks>
|
||||
IEnumerable<IContent> GetContentForRelease(DateTime date);
|
||||
|
||||
/// <summary>
|
||||
/// Get the count of published items
|
||||
/// </summary>
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <remarks>
|
||||
/// <para>This can be optimized and bypass all deep cloning.</para>
|
||||
/// </remarks>
|
||||
int? GetIdByIsoCode(string isoCode);
|
||||
int? GetIdByIsoCode(string isoCode, bool throwOnNotFound = true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a language ISO code from its identifier.
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <remarks>
|
||||
/// <para>This can be optimized and bypass all deep cloning.</para>
|
||||
/// </remarks>
|
||||
string GetIsoCodeById(int? id);
|
||||
string GetIsoCodeById(int? id, bool throwOnNotFound = true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default language ISO code.
|
||||
|
||||
@@ -265,7 +265,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// create prepared sql
|
||||
// ensure it's single-line as NPoco PagingHelper has issues with multi-lines
|
||||
psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments);
|
||||
|
||||
|
||||
// replace the magic culture parameter (see DocumentRepository.GetBaseQuery())
|
||||
if (!ordering.Culture.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -359,7 +359,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// fixme - what if it is NOT a document but a ... media or whatever?
|
||||
// previously, we inserted the join+select *here* so we were sure to have it,
|
||||
// but now that's not the case anymore!
|
||||
return "variantName";
|
||||
return "variantName";
|
||||
}
|
||||
|
||||
// previously, we'd accept anything and just sanitize it - not anymore
|
||||
|
||||
@@ -190,8 +190,8 @@ AND umbracoNode.nodeObjectType = @objectType",
|
||||
SortOrder = allowedContentType.SortOrder
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//Insert Tabs
|
||||
foreach (var propertyGroup in entity.PropertyGroups)
|
||||
{
|
||||
@@ -328,14 +328,14 @@ AND umbracoNode.id <> @id",
|
||||
// We check if the entity's own PropertyTypes has been modified and then also check
|
||||
// any of the property groups PropertyTypes has been modified.
|
||||
// This specifically tells us if any property type collections have changed.
|
||||
if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyGroups.Any(x => x.IsPropertyDirty("PropertyTypes")))
|
||||
if (entity.IsPropertyDirty("NoGroupPropertyTypes") || entity.PropertyGroups.Any(x => x.IsPropertyDirty("PropertyTypes")))
|
||||
{
|
||||
var dbPropertyTypes = Database.Fetch<PropertyTypeDto>("WHERE contentTypeId = @Id", new { entity.Id });
|
||||
var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id);
|
||||
var dbPropertyTypeIds = 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);
|
||||
var propertyTypeToDeleteIds = dbPropertyTypeIds.Except(entityPropertyTypes);
|
||||
foreach (var propertyTypeId in propertyTypeToDeleteIds)
|
||||
DeletePropertyType(entity.Id, propertyTypeId);
|
||||
}
|
||||
|
||||
// Delete tabs ... by excepting entries from db with entries from collections.
|
||||
@@ -620,7 +620,7 @@ AND umbracoNode.id <> @id",
|
||||
var sqlDelete = Sql()
|
||||
.Delete<RedirectUrlDto>()
|
||||
.WhereIn((System.Linq.Expressions.Expression<Func<RedirectUrlDto, object>>)(x => x.ContentKey), sqlSelect);
|
||||
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
}
|
||||
|
||||
@@ -690,7 +690,7 @@ AND umbracoNode.id <> @id",
|
||||
//first clear out any existing names that might already exists under the default lang
|
||||
//there's 2x tables to update
|
||||
|
||||
//clear out the versionCultureVariation table
|
||||
//clear out the versionCultureVariation table
|
||||
var sqlSelect = Sql().Select<ContentVersionCultureVariationDto>(x => x.Id)
|
||||
.From<ContentVersionCultureVariationDto>()
|
||||
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>(x => x.Id, x => x.VersionId)
|
||||
|
||||
@@ -83,13 +83,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var translator = new SqlTranslator<IContent>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
|
||||
sql // fixme why?
|
||||
.OrderBy<NodeDto>(x => x.Level)
|
||||
.OrderBy<NodeDto>(x => x.SortOrder);
|
||||
AddGetByQueryOrderBy(sql);
|
||||
|
||||
return MapDtosToContent(Database.Fetch<DocumentDto>(sql));
|
||||
}
|
||||
|
||||
private void AddGetByQueryOrderBy(Sql<ISqlContext> sql)
|
||||
{
|
||||
sql // fixme why - this should be Path
|
||||
.OrderBy<NodeDto>(x => x.Level)
|
||||
.OrderBy<NodeDto>(x => x.SortOrder);
|
||||
}
|
||||
|
||||
protected override Sql<ISqlContext> GetBaseQuery(QueryType queryType)
|
||||
{
|
||||
return GetBaseQuery(queryType, true);
|
||||
@@ -121,7 +126,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
.Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 =>
|
||||
r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv")))
|
||||
|
||||
// select the variant name, coalesce to the invariant name, as "variantName"
|
||||
// select the variant name, coalesce to the invariant name, as "variantName"
|
||||
.AndSelect(VariantNameSqlExpression + " AS variantName");
|
||||
break;
|
||||
}
|
||||
@@ -143,11 +148,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
.On<ContentVersionDto, DocumentVersionDto>((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv")
|
||||
.On<DocumentDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv")
|
||||
|
||||
//fixme - should we be joining this when the query type is not single/many?
|
||||
// left join on optional culture variation
|
||||
//the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code
|
||||
.LeftJoin<ContentVersionCultureVariationDto>(nested =>
|
||||
nested.InnerJoin<LanguageDto>("lang").On<ContentVersionCultureVariationDto, LanguageDto>((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv")
|
||||
.On<ContentVersionDto, ContentVersionCultureVariationDto>((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv");
|
||||
nested.InnerJoin<LanguageDto>("lang").On<ContentVersionCultureVariationDto, LanguageDto>((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv")
|
||||
.On<ContentVersionDto, ContentVersionCultureVariationDto>((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv");
|
||||
|
||||
sql
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
@@ -182,6 +188,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
var list = new List<string>
|
||||
{
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentSchedule + " WHERE nodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.RedirectUrl + " WHERE contentKey IN (SELECT uniqueId FROM " + Constants.DatabaseSchema.Tables.Node + " WHERE id = @id)",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.User2NodeNotify + " WHERE nodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.UserGroup2NodePermission + " WHERE nodeId = @id",
|
||||
@@ -260,7 +267,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// however, it's not just so we have access to AddingEntity
|
||||
// there are tons of things at the end of the methods, that can only work with a true Content
|
||||
// and basically, the repository requires a Content, not an IContent
|
||||
var content = (Content) entity;
|
||||
var content = (Content)entity;
|
||||
|
||||
content.AddingEntity();
|
||||
var publishing = content.PublishedState == PublishedState.Publishing;
|
||||
@@ -319,7 +326,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto;
|
||||
contentVersionDto.NodeId = nodeDto.NodeId;
|
||||
contentVersionDto.Current = !publishing;
|
||||
contentVersionDto.Text = content.Name;
|
||||
Database.Insert(contentVersionDto);
|
||||
content.VersionId = contentVersionDto.Id;
|
||||
|
||||
@@ -357,13 +363,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
// persist the document dto
|
||||
// at that point, when publishing, the entity still has its old Published value
|
||||
// so we need to explicitely update the dto to persist the correct value
|
||||
// so we need to explicitly update the dto to persist the correct value
|
||||
if (content.PublishedState == PublishedState.Publishing)
|
||||
dto.Published = true;
|
||||
dto.NodeId = nodeDto.NodeId;
|
||||
content.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
|
||||
Database.Insert(dto);
|
||||
|
||||
//insert the schedule
|
||||
PersistContentSchedule(content, false);
|
||||
|
||||
// persist the variations
|
||||
if (content.ContentType.VariesByCulture())
|
||||
{
|
||||
@@ -434,10 +443,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// however, it's not just so we have access to AddingEntity
|
||||
// there are tons of things at the end of the methods, that can only work with a true Content
|
||||
// and basically, the repository requires a Content, not an IContent
|
||||
var content = (Content) entity;
|
||||
var content = (Content)entity;
|
||||
|
||||
// check if we need to make any database changes at all
|
||||
if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty())
|
||||
if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished)
|
||||
&& !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty())
|
||||
return; // no change to save, do nothing, don't even update dates
|
||||
|
||||
// whatever we do, we must check that we are saving the current version
|
||||
@@ -491,7 +501,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
documentVersionDto.Published = true; // now published
|
||||
contentVersionDto.Current = false; // no more current
|
||||
contentVersionDto.Text = content.Name;
|
||||
}
|
||||
Database.Update(contentVersionDto);
|
||||
Database.Update(documentVersionDto);
|
||||
@@ -574,7 +583,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
// update the document dto
|
||||
// at that point, when un/publishing, the entity still has its old Published value
|
||||
// so we need to explicitely update the dto to persist the correct value
|
||||
// so we need to explicitly update the dto to persist the correct value
|
||||
if (content.PublishedState == PublishedState.Publishing)
|
||||
dto.Published = true;
|
||||
else if (content.PublishedState == PublishedState.Unpublishing)
|
||||
@@ -582,8 +591,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
content.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
|
||||
Database.Update(dto);
|
||||
|
||||
//update the schedule
|
||||
if (content.IsPropertyDirty("ContentSchedule"))
|
||||
PersistContentSchedule(content, true);
|
||||
|
||||
// if entity is publishing, update tags, else leave tags there
|
||||
// means that implicitely unpublished, or trashed, entities *still* have tags in db
|
||||
// means that implicitly unpublished, or trashed, entities *still* have tags in db
|
||||
if (content.PublishedState == PublishedState.Publishing)
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
|
||||
@@ -613,8 +626,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
ClearEntityTags(entity, _tagRepository);
|
||||
}
|
||||
|
||||
// note re. tags: explicitely unpublished entities have cleared tags,
|
||||
// but masked or trashed entitites *still* have tags in the db fixme so what?
|
||||
// note re. tags: explicitly unpublished entities have cleared tags,
|
||||
// but masked or trashed entities *still* have tags in the db fixme so what?
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
@@ -631,6 +644,35 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
//}
|
||||
}
|
||||
|
||||
private void PersistContentSchedule(IContent content, bool update)
|
||||
{
|
||||
var schedules = ContentBaseFactory.BuildScheduleDto(content, LanguageRepository).ToList();
|
||||
|
||||
//remove any that no longer exist
|
||||
if (update)
|
||||
{
|
||||
var ids = schedules.Where(x => x.Model.Id != Guid.Empty).Select(x => x.Model.Id).Distinct();
|
||||
Database.Execute(Sql()
|
||||
.Delete<ContentScheduleDto>()
|
||||
.Where<ContentScheduleDto>(x => x.NodeId == content.Id)
|
||||
.WhereNotIn<ContentScheduleDto>(x => x.Id, ids));
|
||||
}
|
||||
|
||||
//add/update the rest
|
||||
foreach (var schedule in schedules)
|
||||
{
|
||||
if (schedule.Model.Id == Guid.Empty)
|
||||
{
|
||||
schedule.Model.Id = schedule.Dto.Id = Guid.NewGuid();
|
||||
Database.Insert(schedule.Dto);
|
||||
}
|
||||
else
|
||||
{
|
||||
Database.Update(schedule.Dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PersistDeletedItem(IContent entity)
|
||||
{
|
||||
// raise event first else potential FK issues
|
||||
@@ -734,7 +776,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
foreach (var filterClause in filter.GetWhereClauses())
|
||||
{
|
||||
var clauseSql = filterClause.Item1;
|
||||
var clauseArgs = filterClause.Item2;
|
||||
var clauseArgs = filterClause.Item2;
|
||||
|
||||
// replace the name field
|
||||
// we cannot reference an aliased field in a WHERE clause, so have to repeat the expression here
|
||||
@@ -869,6 +911,49 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
#endregion
|
||||
|
||||
#region Schedule
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClearSchedule(DateTime date)
|
||||
{
|
||||
var sql = Sql().Delete<ContentScheduleDto>().Where<ContentScheduleDto>(x => x.Date <= date);
|
||||
Database.Execute(sql);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IContent> GetContentForRelease(DateTime date)
|
||||
{
|
||||
var action = ContentScheduleAction.Release.ToString();
|
||||
|
||||
var sql = GetBaseQuery(QueryType.Many)
|
||||
.WhereIn<NodeDto>(x => x.NodeId, Sql()
|
||||
.Select<ContentScheduleDto>(x => x.NodeId)
|
||||
.From<ContentScheduleDto>()
|
||||
.Where<ContentScheduleDto>(x => x.Action == action && x.Date <= date));
|
||||
|
||||
AddGetByQueryOrderBy(sql);
|
||||
|
||||
return MapDtosToContent(Database.Fetch<DocumentDto>(sql));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IContent> GetContentForExpiration(DateTime date)
|
||||
{
|
||||
var action = ContentScheduleAction.Expire.ToString();
|
||||
|
||||
var sql = GetBaseQuery(QueryType.Many)
|
||||
.WhereIn<NodeDto>(x => x.NodeId, Sql()
|
||||
.Select<ContentScheduleDto>(x => x.NodeId)
|
||||
.From<ContentScheduleDto>()
|
||||
.Where<ContentScheduleDto>(x => x.Action == action && x.Date <= date));
|
||||
|
||||
AddGetByQueryOrderBy(sql);
|
||||
|
||||
return MapDtosToContent(Database.Fetch<DocumentDto>(sql));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override string ApplySystemOrdering(ref Sql<ISqlContext> sql, Ordering ordering)
|
||||
{
|
||||
// note: 'updater' is the user who created the latest draft version,
|
||||
@@ -939,7 +1024,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Content) cached;
|
||||
content[i] = (Content)cached;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -987,6 +1072,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
// load all properties for all documents from database in 1 query - indexed by version id
|
||||
var properties = GetPropertyCollections(temps);
|
||||
var schedule = GetContentSchedule(temps.Select(x => x.Content.Id).ToArray());
|
||||
|
||||
// assign templates and properties
|
||||
foreach (var temp in temps)
|
||||
@@ -997,13 +1083,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template))
|
||||
temp.Content.PublishTemplate = template;
|
||||
|
||||
if (properties.ContainsKey(temp.VersionId))
|
||||
temp.Content.Properties = properties[temp.VersionId];
|
||||
else
|
||||
throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'.");
|
||||
if (properties.ContainsKey(temp.VersionId))
|
||||
temp.Content.Properties = properties[temp.VersionId];
|
||||
else
|
||||
throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'.");
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
temp.Content.ResetDirtyProperties(false);
|
||||
//load in the schedule
|
||||
if (schedule.TryGetValue(temp.Content.Id, out var s))
|
||||
temp.Content.ContentSchedule = s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,6 +1105,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
SetVariations(temp.Content, contentVariations, documentVariations);
|
||||
}
|
||||
|
||||
foreach(var c in content)
|
||||
c.ResetDirtyProperties(false); // reset dirty initial properties (U4-1946)
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -1026,33 +1116,72 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var contentType = _contentTypeRepository.Get(dto.ContentDto.ContentTypeId);
|
||||
var content = ContentBaseFactory.BuildEntity(dto, contentType);
|
||||
|
||||
// get template
|
||||
if (dto.DocumentVersionDto.TemplateId.HasValue && dto.DocumentVersionDto.TemplateId.Value > 0)
|
||||
content.Template = _templateRepository.Get(dto.DocumentVersionDto.TemplateId.Value);
|
||||
|
||||
// get properties - indexed by version id
|
||||
var versionId = dto.DocumentVersionDto.Id;
|
||||
|
||||
// fixme - shall we get published properties or not?
|
||||
//var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0;
|
||||
var publishedVersionId = dto.PublishedVersionDto != null ? dto.PublishedVersionDto.Id : 0;
|
||||
|
||||
var temp = new TempContent<Content>(dto.NodeId, versionId, publishedVersionId, contentType);
|
||||
var ltemp = new List<TempContent<Content>> { temp };
|
||||
var properties = GetPropertyCollections(ltemp);
|
||||
content.Properties = properties[dto.DocumentVersionDto.Id];
|
||||
|
||||
// set variations, if varying
|
||||
if (contentType.VariesByCulture())
|
||||
try
|
||||
{
|
||||
var contentVariations = GetContentVariations(ltemp);
|
||||
var documentVariations = GetDocumentVariations(ltemp);
|
||||
SetVariations(content, contentVariations, documentVariations);
|
||||
content.DisableChangeTracking();
|
||||
|
||||
// get template
|
||||
if (dto.DocumentVersionDto.TemplateId.HasValue && dto.DocumentVersionDto.TemplateId.Value > 0)
|
||||
content.Template = _templateRepository.Get(dto.DocumentVersionDto.TemplateId.Value);
|
||||
|
||||
// get properties - indexed by version id
|
||||
var versionId = dto.DocumentVersionDto.Id;
|
||||
|
||||
// fixme - shall we get published properties or not?
|
||||
//var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0;
|
||||
var publishedVersionId = dto.PublishedVersionDto != null ? dto.PublishedVersionDto.Id : 0;
|
||||
|
||||
var temp = new TempContent<Content>(dto.NodeId, versionId, publishedVersionId, contentType);
|
||||
var ltemp = new List<TempContent<Content>> { temp };
|
||||
var properties = GetPropertyCollections(ltemp);
|
||||
content.Properties = properties[dto.DocumentVersionDto.Id];
|
||||
|
||||
// set variations, if varying
|
||||
if (contentType.VariesByCulture())
|
||||
{
|
||||
var contentVariations = GetContentVariations(ltemp);
|
||||
var documentVariations = GetDocumentVariations(ltemp);
|
||||
SetVariations(content, contentVariations, documentVariations);
|
||||
}
|
||||
|
||||
//load in the schedule
|
||||
var schedule = GetContentSchedule(dto.NodeId);
|
||||
if (schedule.TryGetValue(dto.NodeId, out var s))
|
||||
content.ContentSchedule = s;
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
content.ResetDirtyProperties(false);
|
||||
return content;
|
||||
}
|
||||
finally
|
||||
{
|
||||
content.EnableChangeTracking();
|
||||
}
|
||||
}
|
||||
|
||||
private IDictionary<int, ContentScheduleCollection> GetContentSchedule(params int[] contentIds)
|
||||
{
|
||||
var result = new Dictionary<int, ContentScheduleCollection>();
|
||||
|
||||
var scheduleDtos = Database.FetchByGroups<ContentScheduleDto, int>(contentIds, 2000, batch => Sql()
|
||||
.Select<ContentScheduleDto>()
|
||||
.From<ContentScheduleDto>()
|
||||
.WhereIn<ContentScheduleDto>(x => x.NodeId, batch));
|
||||
|
||||
foreach (var scheduleDto in scheduleDtos)
|
||||
{
|
||||
if (!result.TryGetValue(scheduleDto.NodeId, out var col))
|
||||
col = result[scheduleDto.NodeId] = new ContentScheduleCollection();
|
||||
|
||||
col.Add(new ContentSchedule(scheduleDto.Id,
|
||||
LanguageRepository.GetIsoCodeById(scheduleDto.LanguageId) ?? string.Empty,
|
||||
scheduleDto.Date,
|
||||
scheduleDto.Action == ContentScheduleAction.Release.ToString()
|
||||
? ContentScheduleAction.Release
|
||||
: ContentScheduleAction.Expire));
|
||||
}
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
content.ResetDirtyProperties(false);
|
||||
return content;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetVariations(Content content, IDictionary<int, List<ContentVariation>> contentVariations, IDictionary<int, List<DocumentVariation>> documentVariations)
|
||||
|
||||
@@ -66,6 +66,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (isContent)
|
||||
BuildVariants(entities.Cast<DocumentEntitySlim>());
|
||||
|
||||
//fixme - see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media
|
||||
if (isMedia)
|
||||
BuildProperties(entities, dtos);
|
||||
|
||||
@@ -110,14 +111,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return GetEntity(sql, isContent, isMedia);
|
||||
}
|
||||
|
||||
public virtual IEntitySlim Get(int id)
|
||||
public IEntitySlim Get(int id)
|
||||
{
|
||||
var sql = GetBaseWhere(false, false, false, id);
|
||||
var dto = Database.FirstOrDefault<BaseDto>(sql);
|
||||
return dto == null ? null : BuildEntity(false, false, dto);
|
||||
}
|
||||
|
||||
public virtual IEntitySlim Get(int id, Guid objectTypeId)
|
||||
public IEntitySlim Get(int id, Guid objectTypeId)
|
||||
{
|
||||
var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectTypeId == Constants.ObjectTypes.Media;
|
||||
@@ -126,21 +127,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return GetEntity(sql, isContent, isMedia);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids)
|
||||
public IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids)
|
||||
{
|
||||
return ids.Length > 0
|
||||
? PerformGetAll(objectType, sql => sql.WhereIn<NodeDto>(x => x.NodeId, ids.Distinct()))
|
||||
: PerformGetAll(objectType);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IEntitySlim> GetAll(Guid objectType, params Guid[] keys)
|
||||
public IEnumerable<IEntitySlim> GetAll(Guid objectType, params Guid[] keys)
|
||||
{
|
||||
return keys.Length > 0
|
||||
? PerformGetAll(objectType, sql => sql.WhereIn<NodeDto>(x => x.UniqueId, keys.Distinct()))
|
||||
: PerformGetAll(objectType);
|
||||
}
|
||||
|
||||
private IEnumerable<IEntitySlim> GetEntities(Sql<ISqlContext> sql, bool isContent, bool isMedia)
|
||||
private IEnumerable<IEntitySlim> GetEntities(Sql<ISqlContext> sql, bool isContent, bool isMedia, bool loadMediaProperties)
|
||||
{
|
||||
//isContent is going to return a 1:M result now with the variants so we need to do different things
|
||||
if (isContent)
|
||||
@@ -157,7 +158,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
var entities = dtos.Select(x => BuildEntity(false, isMedia, x)).ToArray();
|
||||
|
||||
if (isMedia)
|
||||
//fixme - see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media
|
||||
if (isMedia && loadMediaProperties)
|
||||
BuildProperties(entities, dtos);
|
||||
|
||||
return entities;
|
||||
@@ -169,17 +171,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var isMedia = objectType == Constants.ObjectTypes.Media;
|
||||
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, objectType, filter);
|
||||
return GetEntities(sql, isContent, isMedia);
|
||||
return GetEntities(sql, isContent, isMedia, true);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TreeEntityPath> GetAllPaths(Guid objectType, params int[] ids)
|
||||
public IEnumerable<TreeEntityPath> GetAllPaths(Guid objectType, params int[] ids)
|
||||
{
|
||||
return ids.Any()
|
||||
? PerformGetAllPaths(objectType, sql => sql.WhereIn<NodeDto>(x => x.NodeId, ids.Distinct()))
|
||||
: PerformGetAllPaths(objectType);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TreeEntityPath> GetAllPaths(Guid objectType, params Guid[] keys)
|
||||
public IEnumerable<TreeEntityPath> GetAllPaths(Guid objectType, params Guid[] keys)
|
||||
{
|
||||
return keys.Any()
|
||||
? PerformGetAllPaths(objectType, sql => sql.WhereIn<NodeDto>(x => x.UniqueId, keys.Distinct()))
|
||||
@@ -193,7 +195,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return Database.Fetch<TreeEntityPath>(sql);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query)
|
||||
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query)
|
||||
{
|
||||
var sqlClause = GetBase(false, false, null);
|
||||
var translator = new SqlTranslator<IUmbracoEntity>(sqlClause, query);
|
||||
@@ -203,7 +205,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return dtos.Select(x => BuildEntity(false, false, x)).ToList();
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType)
|
||||
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType)
|
||||
{
|
||||
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectType == Constants.ObjectTypes.Media;
|
||||
@@ -214,7 +216,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
sql = translator.Translate();
|
||||
sql = AddGroupBy(isContent, isMedia, sql);
|
||||
|
||||
return GetEntities(sql, isContent, isMedia);
|
||||
return GetEntities(sql, isContent, isMedia, true);
|
||||
}
|
||||
|
||||
//fixme - see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media
|
||||
internal IEnumerable<IEntitySlim> GetMediaByQueryWithoutPropertyData(IQuery<IUmbracoEntity> query)
|
||||
{
|
||||
var isContent = false;
|
||||
var isMedia = true;
|
||||
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, null, Constants.ObjectTypes.Media);
|
||||
|
||||
var translator = new SqlTranslator<IUmbracoEntity>(sql, query);
|
||||
sql = translator.Translate();
|
||||
sql = AddGroupBy(isContent, isMedia, sql);
|
||||
|
||||
return GetEntities(sql, isContent, isMedia, false);
|
||||
}
|
||||
|
||||
public UmbracoObjectTypes GetObjectType(int id)
|
||||
@@ -241,6 +258,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return Database.ExecuteScalar<int>(sql) > 0;
|
||||
}
|
||||
|
||||
//fixme - see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media
|
||||
private void BuildProperties(EntitySlim entity, BaseDto dto)
|
||||
{
|
||||
var pdtos = Database.Fetch<PropertyDataDto>(GetPropertyData(dto.VersionId));
|
||||
@@ -248,6 +266,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
BuildProperty(entity, pdto);
|
||||
}
|
||||
|
||||
//fixme - see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media
|
||||
private void BuildProperties(EntitySlim[] entities, List<BaseDto> dtos)
|
||||
{
|
||||
var versionIds = dtos.Select(x => x.VersionId).Distinct().ToList();
|
||||
@@ -263,6 +282,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
}
|
||||
|
||||
//fixme - see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media
|
||||
private void BuildProperty(EntitySlim entity, PropertyDataDto pdto)
|
||||
{
|
||||
// explain ?!
|
||||
|
||||
@@ -236,9 +236,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
// fast way of getting an id for an isoCode - avoiding cloning
|
||||
// _codeIdMap is rebuilt whenever PerformGetAll runs
|
||||
public int? GetIdByIsoCode(string isoCode) => GetIdByIsoCode(isoCode, throwOnNotFound: true);
|
||||
|
||||
private int? GetIdByIsoCode(string isoCode, bool throwOnNotFound)
|
||||
public int? GetIdByIsoCode(string isoCode, bool throwOnNotFound = true)
|
||||
{
|
||||
if (isoCode == null) return null;
|
||||
|
||||
@@ -254,14 +252,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
if (throwOnNotFound)
|
||||
throw new ArgumentException($"Code {isoCode} does not correspond to an existing language.", nameof(isoCode));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// fast way of getting an isoCode for an id - avoiding cloning
|
||||
// _idCodeMap is rebuilt whenever PerformGetAll runs
|
||||
public string GetIsoCodeById(int? id) => GetIsoCodeById(id, throwOnNotFound: true);
|
||||
|
||||
private string GetIsoCodeById(int? id, bool throwOnNotFound)
|
||||
public string GetIsoCodeById(int? id, bool throwOnNotFound = true)
|
||||
{
|
||||
if (id == null) return null;
|
||||
|
||||
@@ -277,6 +274,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
if (throwOnNotFound)
|
||||
throw new ArgumentException($"Id {id} does not correspond to an existing language.", nameof(id));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -211,12 +211,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
//.Where(x => Equals(x, default(TId)) == false)
|
||||
.ToArray();
|
||||
|
||||
if (ids.Length > 2000)
|
||||
// can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities,
|
||||
// the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
|
||||
const int maxParams = 2000;
|
||||
if (ids.Length <= maxParams)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot perform a query with more than 2000 parameters");
|
||||
return CachePolicy.GetAll(ids, PerformGetAll);
|
||||
}
|
||||
|
||||
return CachePolicy.GetAll(ids, PerformGetAll);
|
||||
var entities = new List<TEntity>();
|
||||
foreach (var groupOfIds in ids.InGroupsOf(maxParams))
|
||||
{
|
||||
entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll));
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
template.Path = nodeDto.Path;
|
||||
|
||||
//now do the file work
|
||||
SaveFile(template, dto);
|
||||
SaveFile(template);
|
||||
|
||||
template.ResetDirtyProperties();
|
||||
|
||||
@@ -236,7 +236,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
template.IsMasterTemplate = axisDefs.Any(x => x.ParentId == dto.NodeId);
|
||||
|
||||
//now do the file work
|
||||
SaveFile((Template) entity, dto, originalAlias);
|
||||
SaveFile((Template) entity, originalAlias);
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
@@ -245,7 +245,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
template.GetFileContent = file => GetFileContent((Template) file, false);
|
||||
}
|
||||
|
||||
private void SaveFile(Template template, TemplateDto dto, string originalAlias = null)
|
||||
private void SaveFile(Template template, string originalAlias = null)
|
||||
{
|
||||
string content;
|
||||
|
||||
@@ -275,10 +275,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// once content has been set, "template on disk" are not "on disk" anymore
|
||||
template.Content = content;
|
||||
SetVirtualPath(template);
|
||||
|
||||
if (dto.Design == content) return;
|
||||
dto.Design = content;
|
||||
Database.Update(dto); // though... we don't care about the db value really??!!
|
||||
}
|
||||
|
||||
protected override void PersistDeletedItem(ITemplate entity)
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public IProfile GetProfile(string username)
|
||||
{
|
||||
var dto = GetDtoWith(sql => sql.Where<UserDto>(x => x.UserName == username), false);
|
||||
var dto = GetDtoWith(sql => sql.Where<UserDto>(x => x.Login == username), false);
|
||||
return dto == null ? null : new UserProfile(dto.Id, dto.UserName);
|
||||
}
|
||||
|
||||
|
||||
@@ -334,14 +334,9 @@ ORDER BY TABLE_NAME, INDEX_NAME",
|
||||
switch (systemMethod)
|
||||
{
|
||||
case SystemMethods.NewGuid:
|
||||
return null; // NOT SUPPORTED!
|
||||
//return "NEWID()";
|
||||
return null; // NOT SUPPORTED!
|
||||
case SystemMethods.CurrentDateTime:
|
||||
return "CURRENT_TIMESTAMP";
|
||||
//case SystemMethods.NewSequentialId:
|
||||
// return "NEWSEQUENTIALID()";
|
||||
//case SystemMethods.CurrentUTCDateTime:
|
||||
// return "GETUTCDATE()";
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -53,6 +53,12 @@ namespace Umbraco.Core.PropertyEditors
|
||||
throw new InvalidCastException($"Cannot cast configuration of type {obj.GetType().Name} to {typeof(TConfiguration).Name}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a configuration object into a serialized database value.
|
||||
/// </summary>
|
||||
public static string ToDatabase(object configuration)
|
||||
=> configuration == null ? null : JsonConvert.SerializeObject(configuration, ConfigurationJsonSettings);
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonProperty("defaultConfig")]
|
||||
public virtual IDictionary<string, object> DefaultConfiguration => new Dictionary<string, object>();
|
||||
|
||||
@@ -153,6 +153,9 @@ namespace Umbraco.Core.PropertyEditors
|
||||
set => _defaultConfiguration = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a value editor instance.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user