diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index 1526c54656..e97b03e7e3 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -1,32 +1,92 @@
-# Code Of Conduct
-
-Our informal code of conduct concentrates on the values we, as Umbraco HQ, have set for ourselves and for our community. We expect you to be a friend.
-Instead of listing out all the exact "do's" and "don't's" we want to challenge you to think about our values and apply them:
+# Umbraco Code of Conduct
-If there's a need to talk to Umbraco HQ about anything, please make sure to send a mail to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk).
+## Preamble
-## Be a Friend
+We are the friendly CMS. And our friendliness stems from our values. That's why we have set for ourselves, Umbraco HQ, and the community, five values to guide us in everything we do:
-We welcome and thank you for registering at Our Umbraco. Find below the values that govern Umbraco and which you accept by using Our Umbraco.
+* Trust - We believe in and empower people
+* Respect - We treat others as we would like to be treated
+* Open - We share our thoughts and knowledge
+* Hungry - We want to do things better, best is next
+* Friendly - We want to build long-lasting relationships
-## Trust
+With these values in mind, we want to offer the Umbraco community a code of conduct that specifies a baseline standard of behavior so that people with different social values and communication styles can work together.
-Assume positive intent and try to understand before being understood.
+This code of conduct is based on the widely used Contributor Covenant, as described in [https://www.contributor-covenant.org/](https://www.contributor-covenant.org/)
-## Respect
+## Our Pledge
-Treat others as you would like to be treated.
+We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
-This also goes for treating the HQ with respect. For example: don’t promote products on [our.umbraco.com](https://our.umbraco.com) that directly compete with our commercial offerings which enables us to work for a sustainable Umbraco.
+We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
-## Open
+## Our Standards
+Examples of behavior that contributes to a positive environment for our community include:
-Be honest and straightforward. Tell it as it is. Share thoughts and knowledge and engage in collaboration.
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall community
-## Hungry
+Examples of unacceptable behavior include:
-Don't rest on your laurels and never accept the status quo. Contribute and give back to fellow Umbracians.
+* The use of sexualized language or imagery, and sexual attention or advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
-## Friendly
+## Enforcement Responsibilities
-Don’t judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and build long lasting relationships.
\ No newline at end of file
+Community leaders (e.g. Meetup & festival organizers, moderators, maintainers, ...) are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
+
+Specific enforcement steps are listed in the [Code of Conduct Enforcement Guidelines](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT_ENFORCEMENT.md) document which is an appendix of this document, updated and maintained by the Code of Conduct Team.
+
+## Scope
+This Code of Conduct applies within all community spaces and events supported by Umbraco HQ or using the Umbraco name. It also applies when an individual is officially representing the community in public spaces.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior, may be reported at [conduct@umbraco.com](mailto:conduct@umbraco.com). All complaints will be reviewed and investigated promptly and fairly.
+
+Or alternatively, you can reach out directly to any of the team members behind the address above:
+
+* Sebastiaan Janssen (He, Him - Languages spoken: English, Dutch, Danish(Read)) [sebastiaan@umbraco.com](mailto:sebastiaan@umbraco.com)
+* Ilham Boulghallat (She, Her - Languages spoken: English, French, Arabic) [ilham@umbraco.com](mailto:ilham@umbraco.com)
+* Arnold Visser (He, Him - Languages spoken: English, Dutch) [arnold@umbraco.com](mailto:arnold@umbraco.com)
+
+The review process is done with full respect for the privacy and security of the reporter of any incident.
+
+People with a conflict of interest should exclude themselves or if necessary be excluded by the other team members.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
+
+**1. Correction**
+Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
+
+Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
+
+**2. Warning**
+Community Impact: A violation through a single incident or series of actions.
+
+Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
+
+**3. Temporary Ban**
+Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
+
+Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
+
+**4. Permanent Ban**
+Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
+
+Consequence: A permanent ban from any sort of public interaction within the community.
+
+## Attribution
+This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
+
+This Code of Conduct will be maintained and reviewed by the team listed above.
\ No newline at end of file
diff --git a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md
new file mode 100644
index 0000000000..2bb45644c2
--- /dev/null
+++ b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md
@@ -0,0 +1,57 @@
+# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder
+
+These are the steps followed by the [Umbraco Code of Conduct Team](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT.md) when we respond to an issue or incident brought to our attention by a community member.
+
+This is an appendix to the Code of Conduct and is updated and maintained by the Code of Conduct Team.
+
+To make sure that all reports will be reviewed and investigated promptly and fairly, as highlighted in the Umbraco Code of Conduct, we are following [Mozilla’s Consequence Ladder approach](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md).
+
+This approach helps the Team enforce the Code of Conduct in a structured manner and can be used as a way of communicating escalation. Each time the Team takes an action (warning, ban) the individual is made aware of future consequences. The Team can either follow the order of the levels in the ladder or decide to jump levels. When needed, the team can go directly to a permanent ban.
+
+**Level 0: No Action**
+Recommendations do not indicate a violation of the Code of Conduct.
+
+**Level 1: Simple Warning Issued**
+A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior.
+
+**Level 2: Warning**
+A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally:
+
+* Communication of next-level consequences if behaviors are repeated (according to this ladder).
+
+**Level 3: Warning + Mandatory Cooling Off Period (Access Retained)**
+A private warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally:
+
+* Request to avoid interaction on community messaging platforms (public forums, Our, commenting on issues).
+ * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder.
+* Require they do not interact with others in the report, or those who they suspect are involved in the report.
+* Suggestions for 'out of office' type of message on platforms, to reduce curiosity, or suspicion among those not involved.
+
+**Level 4: Temporary Ban (Access Revoked)**
+Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally:
+
+* 3-6 months imposed break.
+* All accounts deactivated, or blocked during this time (Our, HQ Slack if applicable).
+* Require to avoid interaction on community messaging platforms (public forums, Our, commenting on issues).
+ * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder.
+* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) suspended. (onboarding/reapplication required outside of this process)
+* No attendance at Umbraco events during the ban period.
+* Not allowed to enter Umbraco HQ offices during the ban period.
+* Permission to use the MVP title, if applicable, is revoked during this ban period.
+* The community leaders running events and other initiatives are informed of the ban.
+
+**Level 5: Permanent Ban**
+Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally:
+
+* All accounts deactivated permanently.
+* No attendance at Umbraco events going forward.
+* Not allowed to enter Umbraco HQ offices permanently.
+* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) permanently suspended.
+* Permission to use the MVP title, if applicable, revoked.
+* The community leaders running events and other initiatives are informed of the ban.
+
+
+Sources:
+* [Mozilla Code of Conduct - Enforcement Consequence Ladder](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md)
+* [Drupal Conflict Resolution Policy and Process](https://www.drupal.org/conflict-resolution)
+* [Django Code of Conduct - Enforcement Manual](https://www.djangoproject.com/conduct/enforcement-manual/)
diff --git a/.github/workflows/codeql-config.yml b/.github/workflows/codeql-config.yml
index 59b55e48ec..7bac345491 100644
--- a/.github/workflows/codeql-config.yml
+++ b/.github/workflows/codeql-config.yml
@@ -9,5 +9,6 @@ paths-ignore:
- Umbraco.Tests.AcceptanceTest
- Umbraco.Tests.Benchmarks
- bin
+ - build.tmp
paths:
- - src
\ No newline at end of file
+ - src
diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
deleted file mode 100644
index 3f2aafb259..0000000000
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
- Umbraco.Cms.Core
- 9.0.0
- Umbraco Cms Core Binaries
- Umbraco HQ
- Umbraco HQ
- MIT
- https://umbraco.com/
- https://umbraco.com/dist/nuget/logo-small.png
- false
- Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET Core project.
- Contains the core assemblies needed to run Umbraco Cms
- en-US
- umbraco
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/build/NuSpecs/UmbracoCms.Examine.Lucene.nuspec b/build/NuSpecs/UmbracoCms.Examine.Lucene.nuspec
new file mode 100644
index 0000000000..19d60f27a9
--- /dev/null
+++ b/build/NuSpecs/UmbracoCms.Examine.Lucene.nuspec
@@ -0,0 +1,49 @@
+
+
+
+ Umbraco.Cms.Examine.Lucene
+ 9.0.0
+ Umbraco CMS Examine Binaries
+ Umbraco HQ
+ Umbraco HQ
+ MIT
+ https://umbraco.com/
+ https://umbraco.com/dist/nuget/logo-small.png
+ false
+ Contains the Examine assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET Core project.
+ Contains dll files required to run Examine.
+ en-US
+ umbraco
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
deleted file mode 100644
index f12ada7e64..0000000000
--- a/build/NuSpecs/UmbracoCms.Web.nuspec
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
- Umbraco.Cms.Web
- 9.0.0
- Umbraco Cms Core Binaries
- Umbraco HQ
- Umbraco HQ
- MIT
- https://umbraco.com/
- https://umbraco.com/dist/nuget/logo-small.png
- false
- Contains the web assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET Core project.
- Contains the core assemblies needed to run Umbraco Cms
- en-US
- umbraco
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec
index ef48ce0de2..c031d71704 100644
--- a/build/NuSpecs/UmbracoCms.nuspec
+++ b/build/NuSpecs/UmbracoCms.nuspec
@@ -17,7 +17,8 @@
-
+
+
-
diff --git a/build/build.ps1 b/build/build.ps1
index 58d56fcdfe..bc39d04083 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -194,14 +194,12 @@
# remove extra files
$webAppBin = "$($this.BuildTemp)\WebApp\bin"
- $excludeDirs = @("$($webAppBin)\Config","$($webAppBin)\refs","$($webAppBin)\runtimes","$($webAppBin)\Umbraco","$($webAppBin)\wwwroot")
+ $excludeDirs = @("$($webAppBin)\refs","$($webAppBin)\runtimes","$($webAppBin)\Umbraco","$($webAppBin)\wwwroot")
$excludeFiles = @("$($webAppBin)\appsettings.*","$($webAppBin)\*.deps.json","$($webAppBin)\*.exe","$($webAppBin)\*.config","$($webAppBin)\*.runtimeconfig.json")
$this.RemoveDirectory($excludeDirs)
$this.RemoveFile($excludeFiles)
# copy rest of the files into WebApp
- $this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI.NetCore\Config", "*", "$($this.BuildTemp)\WebApp\config")
- $this.RemoveFile("$($this.BuildTemp)\WebApp\Config\*.Release.*")
$this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI.NetCore\Umbraco", "*", "$($this.BuildTemp)\WebApp\umbraco")
$excludeUmbracoDirs = @("$($this.BuildTemp)\WebApp\umbraco\lib")
$this.RemoveDirectory($excludeUmbracoDirs)
@@ -300,8 +298,6 @@
# create directories
Write-Host "Create directories"
- mkdir "$tmp\Configs" > $null
- mkdir "$tmp\Configs\Lang" > $null
mkdir "$tmp\WebApp\App_Data" > $null
mkdir "$tmp\Templates" > $null
#mkdir "$tmp\WebApp\Media" > $null
@@ -311,14 +307,6 @@
Write-Host "Copy xml documentation"
Copy-Item -force "$tmp\bin\*.xml" "$tmp\WebApp\bin"
- Write-Host "Copy transformed configs and langs"
- # note: exclude imageprocessor/*.config as imageprocessor pkg installs them
- $this.CopyFiles("$tmp\WebApp\config", "*.config", "$tmp\Configs", `
- { -not $_.RelativeName.StartsWith("imageprocessor") })
- $this.CopyFiles("$tmp\WebApp\config", "*.js", "$tmp\Configs")
- $this.CopyFiles("$tmp\WebApp\config\lang", "*.xml", "$tmp\Configs\Lang")
- #$this.CopyFile("$tmp\WebApp\web.config", "$tmp\Configs\web.config.transform")
-
# Write-Host "Copy transformed web.config"
# $this.CopyFile("$src\Umbraco.Web.UI\web.$buildConfiguration.Config.transformed", "$tmp\WebApp\web.config")
@@ -424,7 +412,7 @@
Write-Host "Restore NuGet"
Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log"
$params = "-Source", $nugetsourceUmbraco
- &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log" @params
+ &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\umbraco-netcore-only.sln" > "$($this.BuildTemp)\nuget.restore.log" @params
if (-not $?) { throw "Failed to restore NuGet packages." }
})
@@ -435,17 +423,11 @@
Write-Host "Create NuGet packages"
- &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Core.nuspec" `
- -Properties BuildTmp="$($this.BuildTemp)" `
- -Version "$($this.Version.Semver.ToString())" `
- -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log"
- if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Core." }
-
- &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Web.nuspec" `
- -Properties BuildTmp="$($this.BuildTemp)" `
- -Version "$($this.Version.Semver.ToString())" `
- -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log"
- if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Web." }
+ &dotnet pack "$($this.SolutionRoot)\src\umbraco-netcore-only.sln" `
+ --output "$($this.BuildOutput)" `
+ --verbosity detailed `
+ -c Release `
+ -p:PackageVersion="$($this.Version.Semver.ToString())" > "$($this.BuildTemp)\pack.umbraco.log"
&$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" `
-Properties BuildTmp="$($this.BuildTemp)" `
@@ -453,6 +435,13 @@
-Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cms.log"
if (-not $?) { throw "Failed to pack NuGet UmbracoCms." }
+ &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Examine.Lucene.nuspec" `
+ -Properties BuildTmp="$($this.BuildTemp)" `
+ -Version "$($this.Version.Semver.ToString())" `
+ -Verbosity detailed `
+ -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.examine.lucene.log"
+ if (-not $?) { throw "Failed to pack Nuget UmbracoCms.Lucene.nuspec"}
+
&$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.SqlCe.nuspec" `
-Properties BuildTmp="$($this.BuildTemp)" `
-Version "$($this.Version.Semver.ToString())" `
@@ -477,7 +466,7 @@
$ubuild.DefineMethod("VerifyNuGet",
{
$this.VerifyNuGetConsistency(
- ("UmbracoCms", "UmbracoCms.Core", "UmbracoCms.Web"),
+ ("UmbracoCms"),
("Umbraco.Core", "Umbraco.Infrastructure", "Umbraco.Web.UI.NetCore", "Umbraco.Examine.Lucene", "Umbraco.PublishedCache.NuCache", "Umbraco.Web.Common", "Umbraco.Web.Website", "Umbraco.Web.BackOffice", "Umbraco.Persistence.SqlCe"))
if ($this.OnError()) { return }
})
diff --git a/build/templates/UmbracoSolution/.template.config/template.json b/build/templates/UmbracoSolution/.template.config/template.json
index 7f69b5b0e8..a85a4f4af8 100644
--- a/build/templates/UmbracoSolution/.template.config/template.json
+++ b/build/templates/UmbracoSolution/.template.config/template.json
@@ -15,7 +15,7 @@
"version": {
"type": "parameter",
"datatype": "string",
- "defaultValue": "0.5.0-alpha003",
+ "defaultValue": "9.0.0-alpha004",
"description": "The version of Umbraco to load using NuGet",
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
},
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index cdce38df2f..c4346a0603 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -2,12 +2,20 @@
9.0.0
9.0.0
- 9.0.0-beta001
+ 9.0.0-alpha004
9.0.0
9.0
en-US
Umbraco CMS
Copyright © Umbraco 2021
+ Umbraco HQ
+ https://umbraco.com/
+ https://umbraco.com/dist/nuget/logo-small.png
+ https://opensource.org/licenses/MIT
+ false
+ umbraco
+ git
+ https://github.com/umbraco/umbraco-cms
diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs
index 9f082df104..6a17356a68 100644
--- a/src/Umbraco.Core/Cache/CacheKeys.cs
+++ b/src/Umbraco.Core/Cache/CacheKeys.cs
@@ -14,5 +14,10 @@
public const string MacroFromAliasCacheKey = "macroFromAlias_";
public const string UserGroupGetByAliasCacheKeyPrefix = "UserGroupRepository_GetByAlias_";
+
+ public const string UserAllContentStartNodesPrefix = "AllContentStartNodes";
+ public const string UserAllMediaStartNodesPrefix = "AllMediaStartNodes";
+ public const string UserMediaStartNodePathsPrefix = "MediaStartNodePaths";
+ public const string UserContentStartNodePathsPrefix = "ContentStartNodePaths";
}
}
diff --git a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs
index 9fd2ed8fda..0932725fe4 100644
--- a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs
@@ -22,14 +22,16 @@ namespace Umbraco.Cms.Core.Cache
public class JsonPayload
{
//[JsonConstructor]
- public JsonPayload(int id, string username)
+ public JsonPayload(int id, string username, bool removed)
{
Id = id;
Username = username;
+ Removed = removed;
}
public int Id { get; }
public string Username { get; }
+ public bool Removed { get; }
}
#region Define
@@ -54,13 +56,13 @@ namespace Umbraco.Cms.Core.Cache
public override void Refresh(int id)
{
- ClearCache(new JsonPayload(id, null));
+ ClearCache(new JsonPayload(id, null, false));
base.Refresh(id);
}
public override void Remove(int id)
{
- ClearCache(new JsonPayload(id, null));
+ ClearCache(new JsonPayload(id, null, false));
base.Remove(id);
}
diff --git a/src/Umbraco.Core/Cache/UserCacheRefresher.cs b/src/Umbraco.Core/Cache/UserCacheRefresher.cs
index 0e8b749e50..6cb3eb7f88 100644
--- a/src/Umbraco.Core/Cache/UserCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/UserCacheRefresher.cs
@@ -40,7 +40,14 @@ namespace Umbraco.Cms.Core.Cache
{
var userCache = AppCaches.IsolatedCaches.Get();
if (userCache)
+ {
userCache.Result.Clear(RepositoryCacheKeys.GetKey(id));
+ userCache.Result.ClearByKey(CacheKeys.UserContentStartNodePathsPrefix + id);
+ userCache.Result.ClearByKey(CacheKeys.UserMediaStartNodePathsPrefix + id);
+ userCache.Result.ClearByKey(CacheKeys.UserAllContentStartNodesPrefix + id);
+ userCache.Result.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id);
+ }
+
base.Remove(id);
}
diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs
index 4ec46bbda0..7d0a8fdb09 100644
--- a/src/Umbraco.Core/Composing/TypeFinder.cs
+++ b/src/Umbraco.Core/Composing/TypeFinder.cs
@@ -22,8 +22,8 @@ namespace Umbraco.Cms.Core.Composing
private readonly object _localFilteredAssemblyCacheLocker = new object();
private readonly List _notifiedLoadExceptionAssemblies = new List();
private static readonly ConcurrentDictionary TypeNamesCache = new ConcurrentDictionary();
- private readonly string[] _assembliesAcceptingLoadExceptions;
+ private readonly ITypeFinderConfig _typeFinderConfig;
// used for benchmark tests
internal bool QueryWithReferencingAssemblies = true;
@@ -32,17 +32,37 @@ namespace Umbraco.Cms.Core.Composing
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_assemblyProvider = assemblyProvider;
_runtimeHash = runtimeHash;
- _assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty();
+ _typeFinderConfig = typeFinderConfig;
+ }
+
+ private string[] _assembliesAcceptingLoadExceptions = null;
+
+ private string[] AssembliesAcceptingLoadExceptions
+ {
+ get
+ {
+ if (_assembliesAcceptingLoadExceptions is not null)
+ {
+ return _assembliesAcceptingLoadExceptions;
+ }
+
+ _assembliesAcceptingLoadExceptions =
+ _typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ??
+ Array.Empty();
+
+ return _assembliesAcceptingLoadExceptions;
+ }
}
+
private bool AcceptsLoadExceptions(Assembly a)
{
- if (_assembliesAcceptingLoadExceptions.Length == 0)
+ if (AssembliesAcceptingLoadExceptions.Length == 0)
return false;
- if (_assembliesAcceptingLoadExceptions.Length == 1 && _assembliesAcceptingLoadExceptions[0] == "*")
+ if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*")
return true;
var name = a.GetName().Name; // simple name of the assembly
- return _assembliesAcceptingLoadExceptions.Any(pattern =>
+ return AssembliesAcceptingLoadExceptions.Any(pattern =>
{
if (pattern.Length > name.Length) return false; // pattern longer than name
if (pattern.Length == name.Length) return pattern.InvariantEquals(name); // same length, must be identical
diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
index a0fd308490..560835a7e4 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
@@ -53,7 +54,7 @@ namespace Umbraco.Extensions
if (path.StartsWith(hostingEnvironment.ApplicationVirtualPath)) // beware of TrimStart, see U4-2518
path = path.Substring(hostingEnvironment.ApplicationVirtualPath.Length);
- return path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower();
+ return path.TrimStart(Constants.CharArrays.Tilde).TrimStart(Constants.CharArrays.ForwardSlash).Replace('/', '-').Trim().ToLower();
}
}
}
diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs
index 680c47590e..ab6a7e9396 100644
--- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs
+++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Text;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Hosting;
@@ -51,6 +52,16 @@ namespace Umbraco.Cms.Core.Configuration.Grid
_logger.LogError(ex, "Could not parse the contents of grid.editors.config.js into a JSON array '{Json}", sourceString);
}
}
+ else// Read default from embedded file
+ {
+ var assembly = GetType().Assembly;
+ var resourceStream = assembly.GetManifestResourceStream(
+ "Umbraco.Cms.Core.EmbeddedResources.Grid.grid.editors.config.js");
+
+ using var reader = new StreamReader(resourceStream, Encoding.UTF8);
+ var sourceString = reader.ReadToEnd();
+ editors.AddRange(_jsonSerializer.Deserialize>(sourceString));
+ }
// add manifest editors, skip duplicates
foreach (var gridEditor in _manifestParser.Manifest.GridEditors)
diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
index 6738956686..42fccd0ca5 100644
--- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
@@ -180,7 +180,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
///
/// Gets or sets a value for the collection of file extensions that are disallowed for upload.
///
- public IEnumerable DisallowedUploadFiles { get; set; } = new[] { "ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd" };
+ public IEnumerable DisallowedUploadFiles { get; set; } = new[] { "ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd", "xamlx" };
///
/// Gets or sets a value for the collection of file extensions that are allowed for upload.
diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
index f8cc97acb8..a31edd5a03 100644
--- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
@@ -1,6 +1,8 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
+using System;
+
namespace Umbraco.Cms.Core.Configuration.Models
{
///
@@ -25,9 +27,9 @@ namespace Umbraco.Cms.Core.Configuration.Models
public string ReservedPaths { get; set; } = StaticReservedPaths;
///
- /// Gets or sets a value for the timeout in minutes.
+ /// Gets or sets a value for the timeout
///
- public int TimeOutInMinutes { get; set; } = 20;
+ public TimeSpan TimeOut{ get; set; } = TimeSpan.FromMinutes(20);
///
/// Gets or sets a value for the default UI language.
@@ -124,5 +126,14 @@ namespace Umbraco.Cms.Core.Configuration.Models
/// Gets a value indicating whether SMTP is configured.
///
public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host);
+
+ ///
+ /// An int value representing the time in milliseconds to lock the database for a write operation
+ ///
+ ///
+ /// The default value is 5000 milliseconds
+ ///
+ /// The timeout in milliseconds.
+ public TimeSpan SqlWriteLockTimeOut { get; } = TimeSpan.FromMilliseconds(5000);
}
}
diff --git a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs
new file mode 100644
index 0000000000..cdd88ca409
--- /dev/null
+++ b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using Umbraco.Cms.Core.Models.ContentEditing;
+
+namespace Umbraco.Cms.Core.Configuration.Models
+{
+ public class RichTextEditorSettings
+ {
+ private static readonly string[] s_default_plugins = new[]
+ {
+ "paste",
+ "anchor",
+ "charmap",
+ "table",
+ "lists",
+ "advlist",
+ "hr",
+ "autolink",
+ "directionality",
+ "tabfocus",
+ "searchreplace"
+ };
+ private static readonly RichTextEditorCommand[] s_default_commands = new []
+ {
+ new RichTextEditorCommand(){Alias = "ace" , Name = "Source code editor" , Mode = RichTextEditorCommandMode.Insert},
+ new RichTextEditorCommand(){Alias = "removeformat" , Name = "Remove format" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "undo" , Name = "Undo" , Mode = RichTextEditorCommandMode.Insert},
+ new RichTextEditorCommand(){Alias = "redo" , Name = "Redo" , Mode = RichTextEditorCommandMode.Insert},
+ new RichTextEditorCommand(){Alias = "cut" , Name = "Cut" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "copy" , Name = "Copy" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "paste" , Name = "Paste" , Mode = RichTextEditorCommandMode.All},
+ new RichTextEditorCommand(){Alias = "styleselect" , Name = "Style select" , Mode = RichTextEditorCommandMode.All},
+ new RichTextEditorCommand(){Alias = "bold" , Name = "Bold" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "italic" , Name = "Italic" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "underline" , Name = "Underline" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "strikethrough" , Name = "Strikethrough" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "alignleft" , Name = "Justify left" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "aligncenter" , Name = "Justify center" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "alignright" , Name = "Justify right" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "alignjustify" , Name = "Justify full" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "bullist" , Name = "Bullet list" , Mode = RichTextEditorCommandMode.All},
+ new RichTextEditorCommand(){Alias = "numlist" , Name = "Numbered list" , Mode = RichTextEditorCommandMode.All},
+ new RichTextEditorCommand(){Alias = "outdent" , Name = "Decrease indent" , Mode = RichTextEditorCommandMode.All},
+ new RichTextEditorCommand(){Alias = "indent" , Name = "Increase indent" , Mode = RichTextEditorCommandMode.All},
+ new RichTextEditorCommand(){Alias = "link" , Name = "Insert/edit link" , Mode = RichTextEditorCommandMode.All},
+ new RichTextEditorCommand(){Alias = "unlink" , Name = "Remove link" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "anchor" , Name = "Anchor" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "umbmediapicker" , Name = "Image" , Mode = RichTextEditorCommandMode.Insert},
+ new RichTextEditorCommand(){Alias = "umbmacro" , Name = "Macro" , Mode = RichTextEditorCommandMode.All},
+ new RichTextEditorCommand(){Alias = "table" , Name = "Table" , Mode = RichTextEditorCommandMode.Insert},
+ new RichTextEditorCommand(){Alias = "umbembeddialog" , Name = "Embed" , Mode = RichTextEditorCommandMode.Insert},
+ new RichTextEditorCommand(){Alias = "hr" , Name = "Horizontal rule" , Mode = RichTextEditorCommandMode.Insert},
+ new RichTextEditorCommand(){Alias = "subscript" , Name = "Subscript" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "superscript" , Name = "Superscript" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "charmap" , Name = "Character map" , Mode = RichTextEditorCommandMode.Insert},
+ new RichTextEditorCommand(){Alias = "rtl" , Name = "Right to left" , Mode = RichTextEditorCommandMode.Selection},
+ new RichTextEditorCommand(){Alias = "ltr" , Name = "Left to right" , Mode = RichTextEditorCommandMode.Selection},
+ };
+
+ private static readonly IDictionary s_default_custom_config = new Dictionary()
+ {
+ ["entity_encoding"] = "raw"
+ };
+
+ public RichTextEditorCommand[] Commands { get; set; } = s_default_commands;
+ public string[] Plugins { get; set; } = s_default_plugins;
+ public IDictionary CustomConfig { get; set; } = s_default_custom_config;
+ public string ValidElements { get; set; } = "+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*]";
+ public string InvalidElements { get; set; } = "font";
+
+ public class RichTextEditorCommand
+ {
+ public string Alias { get; set; }
+ public string Name { get; set; }
+ public RichTextEditorCommandMode Mode { get; set; }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs b/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs
index f8779d817c..26c3ee72c4 100644
--- a/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs
@@ -19,6 +19,12 @@ namespace Umbraco.Cms.Core.Configuration.Models
///
public bool InstallUnattended { get; set; } = false;
+ ///
+ /// Gets or sets a value indicating whether unattended upgrades are enabled.
+ ///
+ public bool UpgradeUnattended { get; set; } = false;
+
+
///
/// Gets or sets a value to use for creating a user with a name for Unattended Installs
///
diff --git a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs
index b963bddc06..5a7a0ad2f5 100644
--- a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs
+++ b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs
@@ -1,6 +1,7 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
+using System;
using Microsoft.Extensions.Options;
namespace Umbraco.Cms.Core.Configuration.Models.Validation
@@ -19,10 +20,29 @@ namespace Umbraco.Cms.Core.Configuration.Models.Validation
return ValidateOptionsResult.Fail(message);
}
+ if (!ValidateSqlWriteLockTimeOutSetting(options.SqlWriteLockTimeOut, out var message2))
+ {
+ return ValidateOptionsResult.Fail(message2);
+ }
+
return ValidateOptionsResult.Success;
}
private bool ValidateSmtpSetting(SmtpSettings value, out string message) =>
ValidateOptionalEntry($"{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.Smtp)}", value, "A valid From email address is required", out message);
+
+ private bool ValidateSqlWriteLockTimeOutSetting(TimeSpan configuredTimeOut, out string message) {
+ // Only apply this setting if it's not excessively high or low
+ const int minimumTimeOut = 100;
+ const int maximumTimeOut = 20000;
+ if (configuredTimeOut.TotalMilliseconds < minimumTimeOut || configuredTimeOut.TotalMilliseconds > maximumTimeOut) // between 0.1 and 20 seconds
+ {
+ message = $"The `{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.SqlWriteLockTimeOut)}` setting is not between the minimum of {minimumTimeOut} ms and maximum of {maximumTimeOut} ms";
+ return false;
+ }
+
+ message = string.Empty;
+ return true;
+ }
}
}
diff --git a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs
index 3d620ee9e5..0f397a749f 100644
--- a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs
+++ b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs
@@ -1,6 +1,6 @@
-using System.Configuration;
-using System.IO;
+using System.IO;
using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.Exceptions;
using Umbraco.Cms.Core.Hosting;
namespace Umbraco.Extensions
@@ -30,7 +30,7 @@ namespace Umbraco.Extensions
// unless AcceptUnsafeModelsDirectory and then everything is OK.
if (!Path.IsPathRooted(root))
- throw new ConfigurationErrorsException($"Root is not rooted \"{root}\".");
+ throw new ConfigurationException($"Root is not rooted \"{root}\".");
if (config.StartsWith("~/"))
{
@@ -43,7 +43,7 @@ namespace Umbraco.Extensions
root = Path.GetFullPath(root);
if (!dir.StartsWith(root) && !acceptUnsafe)
- throw new ConfigurationErrorsException($"Invalid models directory \"{config}\".");
+ throw new ConfigurationException($"Invalid models directory \"{config}\".");
return dir;
}
@@ -51,7 +51,7 @@ namespace Umbraco.Extensions
if (acceptUnsafe)
return Path.GetFullPath(config);
- throw new ConfigurationErrorsException($"Invalid models directory \"{config}\".");
+ throw new ConfigurationException($"Invalid models directory \"{config}\".");
}
}
}
diff --git a/src/Umbraco.Core/Constants-CharArrays.cs b/src/Umbraco.Core/Constants-CharArrays.cs
new file mode 100644
index 0000000000..0d1722f7eb
--- /dev/null
+++ b/src/Umbraco.Core/Constants-CharArrays.cs
@@ -0,0 +1,132 @@
+namespace Umbraco.Cms.Core
+{
+ public static partial class Constants
+ {
+ ///
+ /// Char Arrays to avoid allocations
+ ///
+ public static class CharArrays
+ {
+ ///
+ /// Char array containing only /
+ ///
+ public static readonly char[] ForwardSlash = new char[] { '/' };
+
+ ///
+ /// Char array containing only \
+ ///
+ public static readonly char[] Backslash = new char[] { '\\' };
+
+ ///
+ /// Char array containing only '
+ ///
+ public static readonly char[] SingleQuote = new char[] { '\'' };
+
+ ///
+ /// Char array containing only "
+ ///
+ public static readonly char[] DoubleQuote = new char[] { '\"' };
+
+
+ ///
+ /// Char array containing ' "
+ ///
+ public static readonly char[] DoubleQuoteSingleQuote = new char[] { '\"', '\'' };
+
+ ///
+ /// Char array containing only _
+ ///
+ public static readonly char[] Underscore = new char[] { '_' };
+
+ ///
+ /// Char array containing \n \r
+ ///
+ public static readonly char[] LineFeedCarriageReturn = new char[] { '\n', '\r' };
+
+
+ ///
+ /// Char array containing \n
+ ///
+ public static readonly char[] LineFeed = new char[] { '\n' };
+
+ ///
+ /// Char array containing only ,
+ ///
+ public static readonly char[] Comma = new char[] { ',' };
+
+ ///
+ /// Char array containing only &
+ ///
+ public static readonly char[] Ampersand = new char[] { '&' };
+
+ ///
+ /// Char array containing only \0
+ ///
+ public static readonly char[] NullTerminator = new char[] { '\0' };
+
+ ///
+ /// Char array containing only .
+ ///
+ public static readonly char[] Period = new char[] { '.' };
+
+ ///
+ /// Char array containing only ~
+ ///
+ public static readonly char[] Tilde = new char[] { '~' };
+ ///
+ /// Char array containing ~ /
+ ///
+ public static readonly char[] TildeForwardSlash = new char[] { '~', '/' };
+
+ ///
+ /// Char array containing only ?
+ ///
+ public static readonly char[] QuestionMark = new char[] { '?' };
+
+ ///
+ /// Char array containing ? &
+ ///
+ public static readonly char[] QuestionMarkAmpersand = new char[] { '?', '&' };
+
+ ///
+ /// Char array containing XML 1.1 whitespace chars
+ ///
+ public static readonly char[] XmlWhitespaceChars = new char[] { ' ', '\t', '\r', '\n' };
+
+ ///
+ /// Char array containing only the Space char
+ ///
+ public static readonly char[] Space = new char[] { ' ' };
+
+ ///
+ /// Char array containing only ;
+ ///
+ public static readonly char[] Semicolon = new char[] { ';' };
+
+ ///
+ /// Char array containing a comma and a space
+ ///
+ public static readonly char[] CommaSpace = new char[] { ',', ' ' };
+
+ ///
+ /// Char array containing _ -
+ ///
+ public static readonly char[] UnderscoreDash = new char[] { '_', '-' };
+
+ ///
+ /// Char array containing =
+ ///
+ public static readonly char[] EqualsChar = new char[] { '=' };
+
+ ///
+ /// Char array containing >
+ ///
+ public static readonly char[] GreaterThan = new char[] { '>' };
+
+ ///
+ /// Char array containing |
+ ///
+ public static readonly char[] VerticalTab = new char[] { '|' };
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs
index 0d62094dad..d596d3feec 100644
--- a/src/Umbraco.Core/Constants-Configuration.cs
+++ b/src/Umbraco.Core/Constants-Configuration.cs
@@ -50,6 +50,7 @@
public const string ConfigTypeFinder = ConfigPrefix + "TypeFinder";
public const string ConfigWebRouting = ConfigPrefix + "WebRouting";
public const string ConfigUserPassword = ConfigPrefix + "Security:UserPassword";
+ public const string ConfigRichTextEditor = ConfigPrefix + "RichTextEditor";
}
}
}
diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs
index 73051f5e95..7885d89679 100644
--- a/src/Umbraco.Core/Constants-Icons.cs
+++ b/src/Umbraco.Core/Constants-Icons.cs
@@ -24,6 +24,26 @@
///
public const string DataType = "icon-autofill";
+ ///
+ /// System dictionary icon
+ ///
+ public const string Dictionary = "icon-book-alt";
+
+ ///
+ /// System generic folder icon
+ ///
+ public const string Folder = "icon-folder";
+
+ ///
+ /// System language icon
+ ///
+ public const string Language = "icon-globe";
+
+ ///
+ /// System logviewer icon
+ ///
+ public const string LogViewer = "icon-box-alt";
+
///
/// System list view icon
///
@@ -69,6 +89,11 @@
///
public const string MemberType = "icon-users";
+ ///
+ /// System packages icon
+ ///
+ public const string Packages = "icon-box";
+
///
/// System property editor icon
///
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
index 47a98ea9e1..c37f8fb760 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
@@ -56,6 +56,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
AddOptions(builder, Constants.Configuration.ConfigWebRouting);
AddOptions(builder, Constants.Configuration.ConfigPlugins);
AddOptions(builder, Constants.Configuration.ConfigUnattended);
+ AddOptions(builder, Constants.Configuration.ConfigRichTextEditor);
return builder;
}
diff --git a/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs b/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs
index 0ecbdbd4ab..7cb97ec57c 100644
--- a/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs
+++ b/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
@@ -16,12 +17,14 @@ namespace Umbraco.Cms.Core.Editors
private readonly IContentService _contentService;
private readonly IMediaService _mediaService;
private readonly IEntityService _entityService;
+ private readonly AppCaches _appCaches;
- public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, IEntityService entityService)
+ public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, IEntityService entityService, AppCaches appCaches)
{
_contentService = contentService;
_mediaService = mediaService;
_entityService = entityService;
+ _appCaches = appCaches;
}
///
@@ -76,6 +79,18 @@ namespace Umbraco.Cms.Core.Editors
if (userGroupAliases != null)
{
var savingGroupAliases = userGroupAliases.ToArray();
+ var existingGroupAliases = savingUser == null
+ ? new string[0]
+ : savingUser.Groups.Select(x => x.Alias).ToArray();
+
+ var addedGroupAliases = savingGroupAliases.Except(existingGroupAliases);
+
+ // As we know the current user is not admin, it is only allowed to use groups that the user do have themselves.
+ var savingGroupAliasesNotAllowed = addedGroupAliases.Except(currentUser.Groups.Select(x=>x.Alias)).ToArray();
+ if (savingGroupAliasesNotAllowed.Any())
+ {
+ return Attempt.Fail("Cannot assign the group(s) '" + string.Join(", ", savingGroupAliasesNotAllowed) + "', the current user is not part of them or admin");
+ }
//only validate any groups that have changed.
//a non-admin user can remove groups and add groups that they have access to
@@ -91,9 +106,7 @@ namespace Umbraco.Cms.Core.Editors
if (userGroupsChanged)
{
// d) A user cannot assign a group to another user that they do not belong to
-
var currentUserGroups = currentUser.Groups.Select(x => x.Alias).ToArray();
-
foreach (var group in newGroups)
{
if (currentUserGroups.Contains(group) == false)
@@ -115,7 +128,7 @@ namespace Umbraco.Cms.Core.Editors
{
if (contentId == Constants.System.Root)
{
- var hasAccess = ContentPermissions.HasPathAccess("-1", currentUser.CalculateContentStartNodeIds(_entityService), Constants.System.RecycleBinContent);
+ var hasAccess = ContentPermissions.HasPathAccess("-1", currentUser.CalculateContentStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinContent);
if (hasAccess == false)
return Attempt.Fail("The current user does not have access to the content root");
}
@@ -123,7 +136,7 @@ namespace Umbraco.Cms.Core.Editors
{
var content = _contentService.GetById(contentId);
if (content == null) continue;
- var hasAccess = currentUser.HasPathAccess(content, _entityService);
+ var hasAccess = currentUser.HasPathAccess(content, _entityService, _appCaches);
if (hasAccess == false)
return Attempt.Fail("The current user does not have access to the content path " + content.Path);
}
@@ -136,7 +149,7 @@ namespace Umbraco.Cms.Core.Editors
{
if (mediaId == Constants.System.Root)
{
- var hasAccess = ContentPermissions.HasPathAccess("-1", currentUser.CalculateMediaStartNodeIds(_entityService), Constants.System.RecycleBinMedia);
+ var hasAccess = ContentPermissions.HasPathAccess("-1", currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinMedia);
if (hasAccess == false)
return Attempt.Fail("The current user does not have access to the media root");
}
@@ -144,7 +157,7 @@ namespace Umbraco.Cms.Core.Editors
{
var media = _mediaService.GetById(mediaId);
if (media == null) continue;
- var hasAccess = currentUser.HasPathAccess(media, _entityService);
+ var hasAccess = currentUser.HasPathAccess(media, _entityService, _appCaches);
if (hasAccess == false)
return Attempt.Fail("The current user does not have access to the media path " + media.Path);
}
diff --git a/src/Umbraco.Web.UI.NetCore/config/grid.editors.config.js b/src/Umbraco.Core/EmbeddedResources/Grid/grid.editors.config.js
similarity index 100%
rename from src/Umbraco.Web.UI.NetCore/config/grid.editors.config.js
rename to src/Umbraco.Core/EmbeddedResources/Grid/grid.editors.config.js
diff --git a/src/Umbraco.Core/Exceptions/ConfigurationException.cs b/src/Umbraco.Core/Exceptions/ConfigurationException.cs
new file mode 100644
index 0000000000..fe711a9823
--- /dev/null
+++ b/src/Umbraco.Core/Exceptions/ConfigurationException.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Cms.Core.Exceptions
+{
+ ///
+ /// An exception that is thrown if the configuration is wrong.
+ ///
+ ///
+ [Serializable]
+ public class ConfigurationException : Exception
+ {
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
+ public ConfigurationException(string message)
+ : base(message)
+ { }
+
+ ///
+ /// Initializes a new instance of the class with a specified error message
+ /// and a reference to the inner exception which is the cause of this exception.
+ ///
+ /// The message that describes the error.
+ /// The inner exception, or null.
+ public ConfigurationException(string message, Exception innerException)
+ : base(message, innerException)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The that holds the serialized object data about the exception being thrown.
+ /// The that contains contextual information about the source or destination.
+ protected ConfigurationException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ { }
+
+ }
+}
diff --git a/src/Umbraco.Core/Extensions/DictionaryExtensions.cs b/src/Umbraco.Core/Extensions/DictionaryExtensions.cs
index 12e8de726f..b524961f7e 100644
--- a/src/Umbraco.Core/Extensions/DictionaryExtensions.cs
+++ b/src/Umbraco.Core/Extensions/DictionaryExtensions.cs
@@ -10,6 +10,7 @@ using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
+using Umbraco.Cms.Core;
namespace Umbraco.Extensions
{
@@ -257,7 +258,7 @@ namespace Umbraco.Extensions
{
builder.Append(String.Format("{0}={1}&", WebUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : WebUtility.UrlEncode(i.Value.ToString())));
}
- return builder.ToString().TrimEnd('&');
+ return builder.ToString().TrimEnd(Constants.CharArrays.Ampersand);
}
/// The get entry ignore case.
diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs
index 70c959d09f..452e409d34 100644
--- a/src/Umbraco.Core/Extensions/StringExtensions.cs
+++ b/src/Umbraco.Core/Extensions/StringExtensions.cs
@@ -40,7 +40,7 @@ namespace Umbraco.Extensions
///
public static int[] GetIdsFromPathReversed(this string path)
{
- var nodeIds = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ var nodeIds = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.TryConvertTo())
.Where(x => x.Success)
.Select(x => x.Result)
@@ -190,7 +190,7 @@ namespace Umbraco.Extensions
//remove any prefixed '&' or '?'
for (var i = 0; i < queryStrings.Length; i++)
{
- queryStrings[i] = queryStrings[i].TrimStart('?', '&').TrimEnd('&');
+ queryStrings[i] = queryStrings[i].TrimStart(Constants.CharArrays.QuestionMarkAmpersand).TrimEnd(Constants.CharArrays.Ampersand);
}
var nonEmpty = queryStrings.Where(x => !x.IsNullOrWhiteSpace()).ToArray();
@@ -1100,7 +1100,7 @@ namespace Umbraco.Extensions
{
return false;
}
- var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
+ var idCheckList = csv.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
return idCheckList.Contains(value);
}
@@ -1115,7 +1115,7 @@ namespace Umbraco.Extensions
fileName = fileName.StripFileExtension();
// underscores and dashes to spaces
- fileName = fileName.ReplaceMany(new[] { '_', '-' }, ' ');
+ fileName = fileName.ReplaceMany(Constants.CharArrays.UnderscoreDash, ' ');
// any other conversions ?
@@ -1123,7 +1123,7 @@ namespace Umbraco.Extensions
fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName);
// Replace multiple consecutive spaces with a single space
- fileName = string.Join(" ", fileName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
+ fileName = string.Join(" ", fileName.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries));
return fileName;
}
diff --git a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs
index b164effdd6..70dd11ff33 100644
--- a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs
+++ b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs
@@ -158,7 +158,7 @@ namespace Umbraco.Extensions
public static StringUdi GetUdi(this Stylesheet entity)
{
if (entity == null) throw new ArgumentNullException("entity");
- return new StringUdi(Constants.UdiEntityType.Stylesheet, entity.Path.TrimStart('/')).EnsureClosed();
+ return new StringUdi(Constants.UdiEntityType.Stylesheet, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed();
}
///
@@ -169,7 +169,7 @@ namespace Umbraco.Extensions
public static StringUdi GetUdi(this Script entity)
{
if (entity == null) throw new ArgumentNullException("entity");
- return new StringUdi(Constants.UdiEntityType.Script, entity.Path.TrimStart('/')).EnsureClosed();
+ return new StringUdi(Constants.UdiEntityType.Script, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed();
}
///
@@ -208,7 +208,7 @@ namespace Umbraco.Extensions
? Constants.UdiEntityType.PartialViewMacro
: Constants.UdiEntityType.PartialView;
- return new StringUdi(entityType, entity.Path.TrimStart('/')).EnsureClosed();
+ return new StringUdi(entityType, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed();
}
///
diff --git a/src/Umbraco.Core/Extensions/UriExtensions.cs b/src/Umbraco.Core/Extensions/UriExtensions.cs
index 5527fc890e..858069edcf 100644
--- a/src/Umbraco.Core/Extensions/UriExtensions.cs
+++ b/src/Umbraco.Core/Extensions/UriExtensions.cs
@@ -2,6 +2,7 @@
// See LICENSE for more details.
using System;
+using Umbraco.Cms.Core;
namespace Umbraco.Extensions
{
@@ -129,12 +130,12 @@ namespace Umbraco.Extensions
if (uri.IsAbsoluteUri)
{
if (path != "/")
- uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path.TrimEnd('/') + uri.Query);
+ uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path.TrimEnd(Constants.CharArrays.ForwardSlash) + uri.Query);
}
else
{
if (path != "/")
- uri = new Uri(path.TrimEnd('/') + uri.Query, UriKind.Relative);
+ uri = new Uri(path.TrimEnd(Constants.CharArrays.ForwardSlash) + uri.Query, UriKind.Relative);
}
return uri;
}
diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs
index 904d6140f2..74f6e2dbc8 100644
--- a/src/Umbraco.Core/GuidUdi.cs
+++ b/src/Umbraco.Core/GuidUdi.cs
@@ -33,7 +33,7 @@ namespace Umbraco.Cms.Core
: base(uriValue)
{
Guid guid;
- if (Guid.TryParse(uriValue.AbsolutePath.TrimStart('/'), out guid) == false)
+ if (Guid.TryParse(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash), out guid) == false)
throw new FormatException("URI \"" + uriValue + "\" is not a GUID entity ID.");
Guid = guid;
diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs
index e799bbdbe8..d0f190868b 100644
--- a/src/Umbraco.Core/IO/IOHelper.cs
+++ b/src/Umbraco.Core/IO/IOHelper.cs
@@ -31,7 +31,7 @@ namespace Umbraco.Cms.Core.IO
retval = virtualPath.Replace("~", _hostingEnvironment.ApplicationVirtualPath);
if (virtualPath.StartsWith("/") && !PathStartsWith(virtualPath, _hostingEnvironment.ApplicationVirtualPath))
- retval = _hostingEnvironment.ApplicationVirtualPath + "/" + virtualPath.TrimStart('/');
+ retval = _hostingEnvironment.ApplicationVirtualPath + "/" + virtualPath.TrimStart(Constants.CharArrays.ForwardSlash);
return retval;
}
@@ -58,14 +58,14 @@ namespace Umbraco.Cms.Core.IO
{
var result = (!string.IsNullOrEmpty(path) && (path.StartsWith("~") || PathStartsWith(path, _hostingEnvironment.ApplicationVirtualPath)))
? _hostingEnvironment.MapPathWebRoot(path)
- : _hostingEnvironment.MapPathWebRoot("~/" + path.TrimStart('/'));
+ : _hostingEnvironment.MapPathWebRoot("~/" + path.TrimStart(Constants.CharArrays.ForwardSlash));
if (result != null) return result;
}
var dirSepChar = Path.DirectorySeparatorChar;
var root = Assembly.GetExecutingAssembly().GetRootDirectorySafe();
- var newPath = path.TrimStart('~', '/').Replace('/', dirSepChar);
+ var newPath = path.TrimStart(Constants.CharArrays.TildeForwardSlash).Replace('/', dirSepChar);
var retval = root + dirSepChar.ToString(CultureInfo.InvariantCulture) + newPath;
return retval;
@@ -141,7 +141,7 @@ namespace Umbraco.Cms.Core.IO
public bool VerifyFileExtension(string filePath, IEnumerable validFileExtensions)
{
var ext = Path.GetExtension(filePath);
- return ext != null && validFileExtensions.Contains(ext.TrimStart('.'));
+ return ext != null && validFileExtensions.Contains(ext.TrimStart(Constants.CharArrays.Period));
}
public abstract bool PathStartsWith(string path, string root, params char[] separators);
diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
index 898a7f0ce4..e517f0be63 100644
--- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs
+++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
@@ -52,7 +52,7 @@ namespace Umbraco.Cms.Core.IO
_rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar);
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
- _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/');
+ _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd(Constants.CharArrays.ForwardSlash);
}
///
@@ -257,12 +257,12 @@ namespace Umbraco.Cms.Core.IO
// if it starts with the root URL, strip it and trim the starting slash to make it relative
// eg "/Media/1234/img.jpg" => "1234/img.jpg"
if (_ioHelper.PathStartsWith(path, _rootUrl, '/'))
- return path.Substring(_rootUrl.Length).TrimStart('/');
+ return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash);
// if it starts with the root path, strip it and trim the starting slash to make it relative
// eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg"
if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/'))
- return path.Substring(_rootPathFwd.Length).TrimStart('/');
+ return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash);
// unchanged - what else?
return path;
@@ -324,7 +324,7 @@ namespace Umbraco.Cms.Core.IO
/// All separators are forward-slashes.
public string GetUrl(string path)
{
- path = EnsureUrlSeparatorChar(path).Trim('/');
+ path = EnsureUrlSeparatorChar(path).Trim(Constants.CharArrays.ForwardSlash);
return _rootUrl + "/" + path;
}
diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs
index 97f2cac668..cc4e792d98 100644
--- a/src/Umbraco.Core/IO/ShadowFileSystem.cs
+++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs
@@ -182,7 +182,7 @@ namespace Umbraco.Cms.Core.IO
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
- var parts = normPath.Split('/');
+ var parts = normPath.Split(Constants.CharArrays.ForwardSlash);
for (var i = 0; i < parts.Length - 1; i++)
{
var dirPath = string.Join("/", parts.Take(i + 1));
@@ -297,7 +297,7 @@ namespace Umbraco.Cms.Core.IO
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
- var parts = normPath.Split('/');
+ var parts = normPath.Split(Constants.CharArrays.ForwardSlash);
for (var i = 0; i < parts.Length - 1; i++)
{
var dirPath = string.Join("/", parts.Take(i + 1));
diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs
index 8d1b1af490..f47cab1c35 100644
--- a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs
+++ b/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs
@@ -44,7 +44,7 @@ namespace Umbraco.Cms.Core.Media.Exif
return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value));
else if (tag == 0x9c9b || tag == 0x9c9c || // Windows tags
tag == 0x9c9d || tag == 0x9c9e || tag == 0x9c9f)
- return new WindowsByteString(etag, Encoding.Unicode.GetString(value).TrimEnd('\0'));
+ return new WindowsByteString(etag, Encoding.Unicode.GetString(value).TrimEnd(Constants.CharArrays.NullTerminator));
}
else if (ifd == IFD.EXIF)
{
@@ -75,7 +75,7 @@ namespace Umbraco.Cms.Core.Media.Exif
hasenc = false;
}
- string val = (hasenc ? enc.GetString(value, 8, value.Length - 8) : enc.GetString(value)).Trim('\0');
+ string val = (hasenc ? enc.GetString(value, 8, value.Length - 8) : enc.GetString(value)).Trim(Constants.CharArrays.NullTerminator);
return new ExifEncodedString(ExifTag.UserComment, val, enc);
}
diff --git a/src/Umbraco.Core/Media/Exif/MathEx.cs b/src/Umbraco.Core/Media/Exif/MathEx.cs
index 8cac15f5b4..dfad9ae7de 100644
--- a/src/Umbraco.Core/Media/Exif/MathEx.cs
+++ b/src/Umbraco.Core/Media/Exif/MathEx.cs
@@ -694,7 +694,7 @@ namespace Umbraco.Cms.Core.Media.Exif
if (s == null)
throw new ArgumentNullException("s");
- string[] sa = s.Split('/');
+ string[] sa = s.Split(Constants.CharArrays.ForwardSlash);
int numerator = 1;
int denominator = 1;
@@ -1322,7 +1322,7 @@ namespace Umbraco.Cms.Core.Media.Exif
if (s == null)
throw new ArgumentNullException("s");
- string[] sa = s.Split('/');
+ string[] sa = s.Split(Constants.CharArrays.ForwardSlash);
uint numerator = 1;
uint denominator = 1;
diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs
index 105b0ce074..dc5529d25f 100644
--- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs
+++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs
@@ -71,7 +71,7 @@ namespace Umbraco.Cms.Core.Media
{
using (var filestream = _mediaFileSystem.OpenFile(filepath))
{
- var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.');
+ var extension = (Path.GetExtension(filepath) ?? "").TrimStart(Constants.CharArrays.Period);
var size = _imageUrlGenerator.IsSupportedImageFormat(extension) ? (ImageSize?)_imageDimensionExtractor.GetDimensions(filestream) : null;
SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment);
}
@@ -105,7 +105,7 @@ namespace Umbraco.Cms.Core.Media
}
else
{
- var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.');
+ var extension = (Path.GetExtension(filepath) ?? "").TrimStart(Constants.CharArrays.Period);
var size = _imageUrlGenerator.IsSupportedImageFormat(extension) ? (ImageSize?)_imageDimensionExtractor.GetDimensions(filestream) : null;
SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment);
}
diff --git a/src/Umbraco.Core/Models/ILogViewerQuery.cs b/src/Umbraco.Core/Models/ILogViewerQuery.cs
new file mode 100644
index 0000000000..3b36f0a9e8
--- /dev/null
+++ b/src/Umbraco.Core/Models/ILogViewerQuery.cs
@@ -0,0 +1,10 @@
+using Umbraco.Cms.Core.Models.Entities;
+
+namespace Umbraco.Cms.Core.Models
+{
+ public interface ILogViewerQuery : IEntity
+ {
+ string Name { get; set; }
+ string Query { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/LogViewerQuery.cs b/src/Umbraco.Core/Models/LogViewerQuery.cs
new file mode 100644
index 0000000000..5addfa705f
--- /dev/null
+++ b/src/Umbraco.Core/Models/LogViewerQuery.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Runtime.Serialization;
+using Umbraco.Cms.Core.Models.Entities;
+
+namespace Umbraco.Cms.Core.Models
+{
+ [Serializable]
+ [DataContract(IsReference = true)]
+ public class LogViewerQuery : EntityBase, ILogViewerQuery
+ {
+ private string _name;
+ private string _query;
+
+ public LogViewerQuery(string name, string query)
+ {
+ Name = name;
+ _query = query;
+ }
+
+ [DataMember]
+ public string Name
+ {
+ get => _name;
+ set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
+ }
+
+ [DataMember]
+ public string Query
+ {
+ get => _query;
+ set => SetPropertyValueAndDetectChanges(value, ref _query, nameof(Query));
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs
index 162032216d..3716e9c6a9 100644
--- a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs
+++ b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs
@@ -592,7 +592,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
return Enumerable.Empty();
var aliases = new List();
- var ancestorIds = parent.Path.Split(',').Select(int.Parse);
+ var ancestorIds = parent.Path.Split(Constants.CharArrays.Comma).Select(int.Parse);
// loop through all content types and return ordered aliases of ancestors
var allContentTypes = _contentTypeService.GetAll().ToArray();
foreach (var ancestorId in ancestorIds)
diff --git a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
index 3631629c7b..767ed8d58a 100644
--- a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
+++ b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
@@ -282,8 +282,8 @@ namespace Umbraco.Cms.Core.Models.Mapping
{
target.AvailableCultures = _textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName);
target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
- target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService), UmbracoObjectTypes.Document, "content/contentRoot", context);
- target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService), UmbracoObjectTypes.Media, "media/mediaRoot", context);
+ target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Document, "content/contentRoot", context);
+ target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Media, "media/mediaRoot", context);
target.CreateDate = source.CreateDate;
target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString();
target.Email = source.Email;
@@ -336,8 +336,8 @@ namespace Umbraco.Cms.Core.Models.Mapping
target.Email = source.Email;
target.EmailHash = source.Email.ToLowerInvariant().Trim().GenerateHash();
target.Name = source.Name;
- target.StartContentIds = source.CalculateContentStartNodeIds(_entityService);
- target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService);
+ target.StartContentIds = source.CalculateContentStartNodeIds(_entityService, _appCaches);
+ target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches);
target.UserId = source.Id;
//we need to map the legacy UserType
diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs
index 3a9dae19d2..7806a7dd52 100644
--- a/src/Umbraco.Core/Models/Membership/User.cs
+++ b/src/Umbraco.Core/Models/Membership/User.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Configuration.Models;
@@ -382,11 +383,10 @@ namespace Umbraco.Cms.Core.Models.Membership
}
}
- ///
- /// This is used as an internal cache for this entity - specifically for calculating start nodes so we don't re-calculated all of the time
- ///
[IgnoreDataMember]
[DoNotClone]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This should not be used, it's currently used for only a single edge case - should probably be removed for netcore")]
internal IDictionary AdditionalData
{
get
diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs
index 0c5f4a7d66..fd173caa73 100644
--- a/src/Umbraco.Core/Models/Membership/UserGroup.cs
+++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs
@@ -21,7 +21,7 @@ namespace Umbraco.Cms.Core.Models.Membership
private string _icon;
private string _name;
private IEnumerable _permissions;
- private readonly List _sectionCollection;
+ private List _sectionCollection;
//Custom comparer for enumerable
private static readonly DelegateEqualityComparer> StringEnumerableComparer =
@@ -104,7 +104,10 @@ namespace Umbraco.Cms.Core.Models.Membership
set => SetPropertyValueAndDetectChanges(value, ref _permissions, nameof(Permissions), StringEnumerableComparer);
}
- public IEnumerable AllowedSections => _sectionCollection;
+ public IEnumerable AllowedSections
+ {
+ get => _sectionCollection;
+ }
public void RemoveAllowedSection(string sectionAlias)
{
@@ -124,5 +127,16 @@ namespace Umbraco.Cms.Core.Models.Membership
}
public int UserCount { get; }
+
+ protected override void PerformDeepClone(object clone)
+ {
+
+ base.PerformDeepClone(clone);
+
+ var clonedEntity = (UserGroup)clone;
+
+ //manually clone the start node props
+ clonedEntity._sectionCollection = new List(_sectionCollection);
+ }
}
}
diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs
index e944107f3f..6fb74c3b86 100644
--- a/src/Umbraco.Core/Models/UserExtensions.cs
+++ b/src/Umbraco.Core/Models/UserExtensions.cs
@@ -90,48 +90,48 @@ namespace Umbraco.Cms.Core.Models
- public static bool HasContentRootAccess(this IUser user, IEntityService entityService)
+ internal static bool HasContentRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches)
{
- return ContentPermissions.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
+ return ContentPermissions.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent);
}
- public static bool HasContentBinAccess(this IUser user, IEntityService entityService)
+ internal static bool HasContentBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches)
{
- return ContentPermissions.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
+ return ContentPermissions.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent);
}
- public static bool HasMediaRootAccess(this IUser user, IEntityService entityService)
+ internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches)
{
- return ContentPermissions.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
+ return ContentPermissions.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia);
}
- public static bool HasMediaBinAccess(this IUser user, IEntityService entityService)
+ internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches)
{
- return ContentPermissions.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
+ return ContentPermissions.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia);
}
- public static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService)
+ public static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService, AppCaches appCaches)
{
if (content == null) throw new ArgumentNullException(nameof(content));
- return ContentPermissions.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
+ return ContentPermissions.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent);
}
- public static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService)
+ public static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService, AppCaches appCaches)
{
if (media == null) throw new ArgumentNullException(nameof(media));
- return ContentPermissions.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
+ return ContentPermissions.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia);
}
- public static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService)
+ public static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
- return ContentPermissions.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
+ return ContentPermissions.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent);
}
- public static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService)
+ public static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
- return ContentPermissions.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
+ return ContentPermissions.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia);
}
///
@@ -144,60 +144,72 @@ namespace Umbraco.Cms.Core.Models
return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias);
}
- // calc. start nodes, combining groups' and user's, and excluding what's in the bin
- public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService)
+ ///
+ /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin
+ ///
+ public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches)
{
- const string cacheKey = "AllContentStartNodes";
- //try to look them up from cache so we don't recalculate
- var valuesInUserCache = user.FromUserCache(cacheKey);
- if (valuesInUserCache != null) return valuesInUserCache;
+ var cacheKey = CacheKeys.UserAllContentStartNodesPrefix + user.Id;
+ var runtimeCache = appCaches.IsolatedCaches.GetOrCreate();
+ var result = runtimeCache.GetCacheItem(cacheKey, () =>
+ {
+ var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray();
+ var usn = user.StartContentIds;
+ var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService);
+ return vals;
+ }, TimeSpan.FromMinutes(2), true);
- var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray();
- var usn = user.StartContentIds;
- var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService);
- user.ToUserCache(cacheKey, vals);
- return vals;
+ return result;
}
- // calc. start nodes, combining groups' and user's, and excluding what's in the bin
- public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService)
+ ///
+ /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches)
{
- const string cacheKey = "AllMediaStartNodes";
- //try to look them up from cache so we don't recalculate
- var valuesInUserCache = user.FromUserCache(cacheKey);
- if (valuesInUserCache != null) return valuesInUserCache;
+ var cacheKey = CacheKeys.UserAllMediaStartNodesPrefix + user.Id;
+ var runtimeCache = appCaches.IsolatedCaches.GetOrCreate();
+ var result = runtimeCache.GetCacheItem(cacheKey, () =>
+ {
+ var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray();
+ var usn = user.StartMediaIds;
+ var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService);
+ return vals;
+ }, TimeSpan.FromMinutes(2), true);
- var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray();
- var usn = user.StartMediaIds;
- var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService);
- user.ToUserCache(cacheKey, vals);
- return vals;
+ return result;
}
- public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService)
+ public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches)
{
- const string cacheKey = "MediaStartNodePaths";
- //try to look them up from cache so we don't recalculate
- var valuesInUserCache = user.FromUserCache(cacheKey);
- if (valuesInUserCache != null) return valuesInUserCache;
+ var cacheKey = CacheKeys.UserMediaStartNodePathsPrefix + user.Id;
+ var runtimeCache = appCaches.IsolatedCaches.GetOrCreate();
+ var result = runtimeCache.GetCacheItem(cacheKey, () =>
+ {
+ var startNodeIds = user.CalculateMediaStartNodeIds(entityService, appCaches);
+ var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray();
+ return vals;
+ }, TimeSpan.FromMinutes(2), true);
- var startNodeIds = user.CalculateMediaStartNodeIds(entityService);
- var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray();
- user.ToUserCache(cacheKey, vals);
- return vals;
+ return result;
}
- public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService)
+ public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches)
{
- const string cacheKey = "ContentStartNodePaths";
- //try to look them up from cache so we don't recalculate
- var valuesInUserCache = user.FromUserCache(cacheKey);
- if (valuesInUserCache != null) return valuesInUserCache;
+ var cacheKey = CacheKeys.UserContentStartNodePathsPrefix + user.Id;
+ var runtimeCache = appCaches.IsolatedCaches.GetOrCreate();
+ var result = runtimeCache.GetCacheItem(cacheKey, () =>
+ {
+ var startNodeIds = user.CalculateContentStartNodeIds(entityService, appCaches);
+ var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray();
+ return vals;
+ }, TimeSpan.FromMinutes(2), true);
- var startNodeIds = user.CalculateContentStartNodeIds(entityService);
- var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray();
- user.ToUserCache(cacheKey, vals);
- return vals;
+ return result;
}
private static bool StartsWithPath(string test, string path)
diff --git a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs
index 14e6790f3c..dc62bc84f6 100644
--- a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs
+++ b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs
@@ -48,14 +48,14 @@ namespace Umbraco.Cms.Core.Packaging
ContentLoadChildNodes = xml.Element("content")?.AttributeValue("loadChildNodes") ?? false,
MediaUdis = xml.Element("media")?.Elements("nodeUdi").Select(x => (GuidUdi)UdiParser.Parse(x.Value)).ToList() ?? new List(),
MediaLoadChildNodes = xml.Element("media")?.AttributeValue("loadChildNodes") ?? false,
- Macros = xml.Element("macros")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
- Templates = xml.Element("templates")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
- Stylesheets = xml.Element("stylesheets")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
- DocumentTypes = xml.Element("documentTypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
+ Macros = xml.Element("macros")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
+ Templates = xml.Element("templates")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
+ Stylesheets = xml.Element("stylesheets")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
+ DocumentTypes = xml.Element("documentTypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
MediaTypes = xml.Element("mediaTypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
- Languages = xml.Element("languages")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
- DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
- DataTypes = xml.Element("datatypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
+ Languages = xml.Element("languages")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
+ DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
+ DataTypes = xml.Element("datatypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(),
Files = xml.Element("files")?.Elements("file").Select(x => x.Value).ToList() ?? new List()
};
diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
index 73004c491d..b09991ae20 100644
--- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
+++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
@@ -75,6 +75,8 @@ namespace Umbraco.Cms.Core
public const string AuditEntry = TableNamePrefix + "Audit";
public const string Consent = TableNamePrefix + "Consent";
public const string UserLogin = TableNamePrefix + "UserLogin";
+
+ public const string LogViewerQuery = TableNamePrefix + "LogViewerQuery";
}
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs
new file mode 100644
index 0000000000..d21cd2aa1e
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs
@@ -0,0 +1,9 @@
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.Persistence.Repositories
+{
+ public interface ILogViewerQueryRepository : IReadWriteQueryRepository
+ {
+ ILogViewerQuery GetByName(string name);
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs
index d4f1d84984..dc1126a3c3 100644
--- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs
@@ -10,6 +10,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
///
/// Represents a data type configuration editor.
///
+ [DataContract]
public class ConfigurationEditor : IConfigurationEditor
{
private IDictionary _defaultConfiguration;
diff --git a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs
index 413f7ee24b..4144c7c0a8 100644
--- a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Runtime.Serialization;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.PropertyEditors
@@ -11,6 +12,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
///
/// Gets the fields.
///
+ [DataMember(Name = "fields")]
List Fields { get; }
///
@@ -22,6 +24,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
/// equivalent of an actual configuration object (ie an instance of TConfiguration, obtained
/// via .
///
+ [DataMember(Name = "defaultConfig")]
IDictionary DefaultConfiguration { get; }
///
diff --git a/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs
index 9b720e4fd8..96af838710 100644
--- a/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs
+++ b/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs
@@ -68,6 +68,9 @@ namespace Umbraco.Cms.Core.PropertyEditors
[ConfigurationField("showContentFirst", "Show Content App First", "boolean", Description = "Enable this to show the content app by default instead of the list view app")]
public bool ShowContentFirst { get; set; }
+ [ConfigurationField("useInfiniteEditor", "Edit in Infinite Editor", "boolean", Description = "Enable this to use infinite editing to edit the content of the list view")]
+ public bool UseInfiniteEditor { get; set; }
+
[DataContract]
public class Property
{
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs
index ff5fed786c..f6523da44f 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs
@@ -55,7 +55,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
if (source == null) return null;
var nodeIds = source.ToString()
- .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
+ .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.Select(UdiParser.Parse)
.ToArray();
return nodeIds;
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
index 4a00f20737..bcaa89b97e 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
@@ -52,7 +52,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker))
{
var nodeIds = source.ToString()
- .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
+ .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.Select(UdiParser.Parse)
.ToArray();
return nodeIds;
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs
index a1f3f82f43..67671ee662 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs
@@ -33,7 +33,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
if (IsRangeDataType(propertyType.DataType.Id))
{
- var rangeRawValues = source.ToString().Split(',');
+ var rangeRawValues = source.ToString().Split(Constants.CharArrays.Comma);
var minimumAttempt = rangeRawValues[0].TryConvertTo();
var maximumAttempt = rangeRawValues[1].TryConvertTo();
diff --git a/src/Umbraco.Core/Routing/AliasUrlProvider.cs b/src/Umbraco.Core/Routing/AliasUrlProvider.cs
index 0eb7eea0a2..8b4c633158 100644
--- a/src/Umbraco.Core/Routing/AliasUrlProvider.cs
+++ b/src/Umbraco.Core/Routing/AliasUrlProvider.cs
@@ -90,7 +90,7 @@ namespace Umbraco.Cms.Core.Routing
yield break;
var umbracoUrlName = node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias);
- var aliases = umbracoUrlName?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
if (aliases == null || aliases.Any() == false)
yield break;
@@ -117,7 +117,7 @@ namespace Umbraco.Cms.Core.Routing
? node.Value(_publishedValueFallback,Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture)
: node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias);
- var aliases = umbracoUrlName?.Split(new [] {','}, StringSplitOptions.RemoveEmptyEntries);
+ var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
if (aliases == null || aliases.Any() == false)
continue;
@@ -138,8 +138,8 @@ namespace Umbraco.Cms.Core.Routing
string CombinePaths(string path1, string path2)
{
- string path = path1.TrimEnd('/') + path2;
- return path == "/" ? path : path.TrimEnd('/');
+ string path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2;
+ return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash);
}
#endregion
diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
index 97c7deec9c..2b1693e03f 100644
--- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
+++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
@@ -180,8 +180,8 @@ namespace Umbraco.Cms.Core.Routing
string CombinePaths(string path1, string path2)
{
- string path = path1.TrimEnd('/') + path2;
- return path == "/" ? path : path.TrimEnd('/');
+ string path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2;
+ return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash);
}
#endregion
diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs
index a6cd90a3c2..76266f9704 100644
--- a/src/Umbraco.Core/Routing/DomainUtilities.cs
+++ b/src/Umbraco.Core/Routing/DomainUtilities.cs
@@ -328,7 +328,7 @@ namespace Umbraco.Cms.Core.Routing
{
var stopNodeId = rootNodeId ?? -1;
- return path.Split(',')
+ return path.Split(Constants.CharArrays.Comma)
.Reverse()
.Select(int.Parse)
.TakeWhile(id => id != stopNodeId)
@@ -349,7 +349,7 @@ namespace Umbraco.Cms.Core.Routing
{
var stopNodeId = rootNodeId ?? -1;
- return path.Split(',')
+ return path.Split(Constants.CharArrays.Comma)
.Reverse()
.Select(int.Parse)
.TakeWhile(id => id != stopNodeId)
diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs
index c138232ef5..86ac97db31 100644
--- a/src/Umbraco.Core/Routing/PublishedRouter.cs
+++ b/src/Umbraco.Core/Routing/PublishedRouter.cs
@@ -413,6 +413,15 @@ namespace Umbraco.Cms.Core.Routing
_logger.LogDebug("Finder {ContentFinderType}", finder.GetType().FullName);
return finder.TryFindContent(request);
});
+
+ _logger.LogDebug(
+ "Found? {Found}, Content: {PublishedContentId}, Template: {TemplateAlias}, Domain: {Domain}, Culture: {Culture}, StatusCode: {StatusCode}",
+ found,
+ request.HasPublishedContent() ? request.PublishedContent.Id : "NULL",
+ request.HasTemplate() ? request.Template?.Alias : "NULL",
+ request.HasDomain() ? request.Domain.ToString() : "NULL",
+ request.Culture ?? "NULL",
+ request.ResponseStatusCode);
}
}
diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs
index 4d349021c4..07adfc1587 100644
--- a/src/Umbraco.Core/Routing/UriUtility.cs
+++ b/src/Umbraco.Core/Routing/UriUtility.cs
@@ -43,7 +43,7 @@ namespace Umbraco.Cms.Core.Routing
public string ToAbsolute(string url)
{
//return ResolveUrl(url);
- url = url.TrimStart('~');
+ url = url.TrimStart(Constants.CharArrays.Tilde);
return _appPathPrefix + url;
}
@@ -104,7 +104,7 @@ namespace Umbraco.Cms.Core.Routing
if (path != "/")
{
- path = path.TrimEnd('/');
+ path = path.TrimEnd(Constants.CharArrays.ForwardSlash);
}
return uri.Rewrite(path);
diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
index 80f17e3c12..6dfdc89583 100644
--- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
+++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
@@ -151,7 +151,7 @@ namespace Umbraco.Extensions
// got a URL, deal with collisions, add URL
default:
// detect collisions, etc
- Attempt hasCollision = await DetectCollisionAsync(content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility);
+ Attempt hasCollision = await DetectCollisionAsync(logger, content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility);
if (hasCollision)
{
result.Add(hasCollision.Result);
@@ -187,7 +187,7 @@ namespace Umbraco.Extensions
else if (!parent.Published)
{
// totally not published
- return UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] {parent.Name}), culture);
+ return UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] { parent.Name }), culture);
}
else
{
@@ -196,10 +196,10 @@ namespace Umbraco.Extensions
}
}
- private static async Task> DetectCollisionAsync(IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility)
+ private static async Task> DetectCollisionAsync(ILogger logger, IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility)
{
// test for collisions on the 'main' URL
- var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute);
+ var uri = new Uri(url.TrimEnd(Constants.CharArrays.ForwardSlash), UriKind.RelativeOrAbsolute);
if (uri.IsAbsoluteUri == false)
{
uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl);
@@ -211,6 +211,16 @@ namespace Umbraco.Extensions
if (!pcr.HasPublishedContent())
{
+ var logMsg = nameof(DetectCollisionAsync) + " did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}";
+ if (pcr.IgnorePublishedContentCollisions)
+ {
+ logger.LogDebug(logMsg, url, uri, culture);
+ }
+ else
+ {
+ logger.LogDebug(logMsg, url, uri, culture);
+ }
+
var urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture);
return Attempt.Succeed(urlInfo);
}
diff --git a/src/Umbraco.Core/Security/ContentPermissions.cs b/src/Umbraco.Core/Security/ContentPermissions.cs
index d137b3628e..3d3ae55a62 100644
--- a/src/Umbraco.Core/Security/ContentPermissions.cs
+++ b/src/Umbraco.Core/Security/ContentPermissions.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
@@ -18,6 +19,7 @@ namespace Umbraco.Cms.Core.Security
private readonly IUserService _userService;
private readonly IContentService _contentService;
private readonly IEntityService _entityService;
+ private readonly AppCaches _appCaches;
public enum ContentAccess
{
@@ -29,11 +31,13 @@ namespace Umbraco.Cms.Core.Security
public ContentPermissions(
IUserService userService,
IContentService contentService,
- IEntityService entityService)
+ IEntityService entityService,
+ AppCaches appCaches)
{
_userService = userService;
_contentService = contentService;
_entityService = entityService;
+ _appCaches = appCaches;
}
public ContentAccess CheckPermissions(
@@ -50,7 +54,7 @@ namespace Umbraco.Cms.Core.Security
if (content == null) return ContentAccess.NotFound;
- var hasPathAccess = user.HasPathAccess(content, _entityService);
+ var hasPathAccess = user.HasPathAccess(content, _entityService, _appCaches);
if (hasPathAccess == false)
return ContentAccess.Denied;
@@ -78,7 +82,7 @@ namespace Umbraco.Cms.Core.Security
if (entity == null) return ContentAccess.NotFound;
- var hasPathAccess = user.HasContentPathAccess(entity, _entityService);
+ var hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches);
if (hasPathAccess == false)
return ContentAccess.Denied;
@@ -119,16 +123,16 @@ namespace Umbraco.Cms.Core.Security
entity = null;
if (nodeId == Constants.System.Root)
- hasPathAccess = user.HasContentRootAccess(_entityService);
+ hasPathAccess = user.HasContentRootAccess(_entityService, _appCaches);
else if (nodeId == Constants.System.RecycleBinContent)
- hasPathAccess = user.HasContentBinAccess(_entityService);
+ hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches);
if (hasPathAccess.HasValue)
return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied;
entity = _entityService.Get(nodeId, UmbracoObjectTypes.Document);
if (entity == null) return ContentAccess.NotFound;
- hasPathAccess = user.HasContentPathAccess(entity, _entityService);
+ hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches);
if (hasPathAccess == false)
return ContentAccess.Denied;
@@ -170,16 +174,16 @@ namespace Umbraco.Cms.Core.Security
contentItem = null;
if (nodeId == Constants.System.Root)
- hasPathAccess = user.HasContentRootAccess(_entityService);
+ hasPathAccess = user.HasContentRootAccess(_entityService, _appCaches);
else if (nodeId == Constants.System.RecycleBinContent)
- hasPathAccess = user.HasContentBinAccess(_entityService);
+ hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches);
if (hasPathAccess.HasValue)
return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied;
contentItem = _contentService.GetById(nodeId);
if (contentItem == null) return ContentAccess.NotFound;
- hasPathAccess = user.HasPathAccess(contentItem, _entityService);
+ hasPathAccess = user.HasPathAccess(contentItem, _entityService, _appCaches);
if (hasPathAccess == false)
return ContentAccess.Denied;
diff --git a/src/Umbraco.Core/Security/MediaPermissions.cs b/src/Umbraco.Core/Security/MediaPermissions.cs
index e74144133d..724049d6b9 100644
--- a/src/Umbraco.Core/Security/MediaPermissions.cs
+++ b/src/Umbraco.Core/Security/MediaPermissions.cs
@@ -1,4 +1,5 @@
using System;
+using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services;
@@ -12,6 +13,7 @@ namespace Umbraco.Cms.Core.Security
{
private readonly IMediaService _mediaService;
private readonly IEntityService _entityService;
+ private readonly AppCaches _appCaches;
public enum MediaAccess
{
@@ -20,10 +22,11 @@ namespace Umbraco.Cms.Core.Security
NotFound
}
- public MediaPermissions(IMediaService mediaService, IEntityService entityService)
+ public MediaPermissions(IMediaService mediaService, IEntityService entityService, AppCaches appCaches)
{
_mediaService = mediaService;
_entityService = entityService;
+ _appCaches = appCaches;
}
///
@@ -52,10 +55,10 @@ namespace Umbraco.Cms.Core.Security
}
var hasPathAccess = (nodeId == Constants.System.Root)
- ? user.HasMediaRootAccess(_entityService)
+ ? user.HasMediaRootAccess(_entityService, _appCaches)
: (nodeId == Constants.System.RecycleBinMedia)
- ? user.HasMediaBinAccess(_entityService)
- : user.HasPathAccess(media, _entityService);
+ ? user.HasMediaBinAccess(_entityService, _appCaches)
+ : user.HasPathAccess(media, _entityService, _appCaches);
return hasPathAccess ? MediaAccess.Granted : MediaAccess.Denied;
}
@@ -66,7 +69,7 @@ namespace Umbraco.Cms.Core.Security
if (media == null) return MediaAccess.NotFound;
- var hasPathAccess = user.HasPathAccess(media, _entityService);
+ var hasPathAccess = user.HasPathAccess(media, _entityService, _appCaches);
return hasPathAccess ? MediaAccess.Granted : MediaAccess.Denied;
}
diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
index f6b236439b..ffa0a38489 100644
--- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
@@ -47,7 +47,7 @@ namespace Umbraco.Extensions
var matches = AnchorRegex.Matches(rteContent);
foreach (Match match in matches)
{
- result.Add(match.Value.Split('\"')[1]);
+ result.Add(match.Value.Split(Constants.CharArrays.DoubleQuote)[1]);
}
return result;
}
diff --git a/src/Umbraco.Core/Services/DashboardService.cs b/src/Umbraco.Core/Services/DashboardService.cs
index 3f806bcc43..d4116f5dd8 100644
--- a/src/Umbraco.Core/Services/DashboardService.cs
+++ b/src/Umbraco.Core/Services/DashboardService.cs
@@ -82,7 +82,7 @@ namespace Umbraco.Cms.Core.Services
if (grantBySectionRules.Length > 0)
{
var allowedSections = sectionService.GetAllowedSections(user.Id).Select(x => x.Alias).ToArray();
- var wantedSections = grantBySectionRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
+ var wantedSections = grantBySectionRules.SelectMany(g => g.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)).ToArray();
if (wantedSections.Intersect(allowedSections).Any())
hasAccess = true;
@@ -93,7 +93,7 @@ namespace Umbraco.Cms.Core.Services
if (hasAccess == false && grantRules.Any())
{
assignedUserGroups = user.Groups.Select(x => x.Alias).ToArray();
- var wantedUserGroups = grantRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
+ var wantedUserGroups = grantRules.SelectMany(g => g.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)).ToArray();
if (wantedUserGroups.Intersect(assignedUserGroups).Any())
hasAccess = true;
@@ -107,7 +107,7 @@ namespace Umbraco.Cms.Core.Services
// check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will
// be denied to see it no matter what
assignedUserGroups = assignedUserGroups ?? user.Groups.Select(x => x.Alias).ToArray();
- var deniedUserGroups = denyRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
+ var deniedUserGroups = denyRules.SelectMany(g => g.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)).ToArray();
if (deniedUserGroups.Intersect(assignedUserGroups).Any())
hasAccess = false;
diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs
index 7206f74964..c06711a91e 100644
--- a/src/Umbraco.Core/Services/UserServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services;
@@ -10,7 +11,7 @@ namespace Umbraco.Extensions
{
public static EntityPermission GetPermissions(this IUserService userService, IUser user, string path)
{
- var ids = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ var ids = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.TryConvertTo())
.Where(x => x.Success)
.Select(x => x.Result)
diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs
index f2b138a938..3435c81780 100644
--- a/src/Umbraco.Core/StringUdi.cs
+++ b/src/Umbraco.Core/StringUdi.cs
@@ -33,7 +33,7 @@ namespace Umbraco.Cms.Core
public StringUdi(Uri uriValue)
: base(uriValue)
{
- Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart('/'));
+ Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash));
}
private static string EscapeUriString(string s)
@@ -46,7 +46,7 @@ namespace Umbraco.Cms.Core
// we want to preserve the / and the unreserved
// so...
- return string.Join("/", s.Split('/').Select(Uri.EscapeDataString));
+ return string.Join("/", s.Split(Constants.CharArrays.ForwardSlash).Select(Uri.EscapeDataString));
}
///
diff --git a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs
index c132c5d592..b6ffeaa57e 100644
--- a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs
+++ b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs
@@ -28,7 +28,7 @@ namespace Umbraco.Cms.Core.Strings.Css
{
// since we already have a string builder in play here, we'll append to it the "hard" way
// instead of using string interpolation (for increased performance)
- foreach (var style in Styles.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
+ foreach (var style in Styles.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries))
{
sb.Append("\t").Append(style.StripNewLines().Trim()).Append(";").Append(Environment.NewLine);
}
diff --git a/src/Umbraco.Core/Strings/Diff.cs b/src/Umbraco.Core/Strings/Diff.cs
index 8486875ad1..b0cf7100de 100644
--- a/src/Umbraco.Core/Strings/Diff.cs
+++ b/src/Umbraco.Core/Strings/Diff.cs
@@ -229,7 +229,7 @@ namespace Umbraco.Cms.Core.Strings
// strip off all cr, only use lf as text line separator.
aText = aText.Replace("\r", "");
- var lines = aText.Split('\n');
+ var lines = aText.Split(Constants.CharArrays.LineFeed);
var codes = new int[lines.Length];
diff --git a/src/Umbraco.Core/Trees/TreeNode.cs b/src/Umbraco.Core/Trees/TreeNode.cs
index c09f7559ca..4e509da259 100644
--- a/src/Umbraco.Core/Trees/TreeNode.cs
+++ b/src/Umbraco.Core/Trees/TreeNode.cs
@@ -103,8 +103,19 @@ namespace Umbraco.Cms.Core.Trees
{
get
{
- // TODO: Is this ever actually used? If not remove, if so, add setter.
return string.Empty;
+
+ //TODO Figure out how to do this, without the model has to know a bout services and config.
+ //
+ // if (IconIsClass)
+ // return string.Empty;
+ //
+ // //absolute path with or without tilde
+ // if (Icon.StartsWith("~") || Icon.StartsWith("/"))
+ // return IOHelper.ResolveUrl("~" + Icon.TrimStart(Constants.CharArrays.Tilde));
+ //
+ // //legacy icon path
+ // return string.Format("{0}images/umbraco/{1}", Current.Configs.Global().Path.EnsureEndsWith("/"), Icon);
}
}
diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Core/UdiRange.cs
index 50f5b88189..250eef7e71 100644
--- a/src/Umbraco.Core/UdiRange.cs
+++ b/src/Umbraco.Core/UdiRange.cs
@@ -70,7 +70,7 @@ namespace Umbraco.Cms.Core
}
var udiUri = uri.Query == string.Empty ? uri : new UriBuilder(uri) { Query = string.Empty }.Uri;
- return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart('?'));
+ return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart(Constants.CharArrays.QuestionMark));
}
public override string ToString()
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index be36173981..ce524a09a1 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -1,9 +1,13 @@
-
+
netstandard2.0
Umbraco.Cms.Core
Umbraco CMS
+ Umbraco.Cms.Core
+ Umbraco CMS Core
+ Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
+ Umbraco CMS
@@ -12,13 +16,13 @@
+
-
@@ -43,4 +47,8 @@
<_Parameter1>DynamicProxyGenAssembly2
+
+
+
+
diff --git a/src/Umbraco.Core/UriUtilityCore.cs b/src/Umbraco.Core/UriUtilityCore.cs
index 8716865a9e..d63692b30a 100644
--- a/src/Umbraco.Core/UriUtilityCore.cs
+++ b/src/Umbraco.Core/UriUtilityCore.cs
@@ -45,7 +45,7 @@ namespace Umbraco.Cms.Core
var pos = Math.Min(pos1, pos2);
var path = pos > 0 ? uri.Substring(0, pos) : uri;
- path = path.TrimEnd('/');
+ path = path.TrimEnd(Constants.CharArrays.ForwardSlash);
if (pos > 0)
path += uri.Substring(pos);
diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs
index ab171659fb..6cbb888965 100644
--- a/src/Umbraco.Core/Xml/XmlHelper.cs
+++ b/src/Umbraco.Core/Xml/XmlHelper.cs
@@ -53,7 +53,7 @@ namespace Umbraco.Cms.Core.Xml
public static bool IsXmlWhitespace(string s)
{
// as per xml 1.1 specs - anything else is significant whitespace
- s = s.Trim(' ', '\t', '\r', '\n');
+ s = s.Trim(Constants.CharArrays.XmlWhitespaceChars);
return s.Length == 0;
}
diff --git a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs
index c9fab6b6fc..3da6b854f8 100644
--- a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs
+++ b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs
@@ -8,10 +8,14 @@ using System.Text;
using System.Text.RegularExpressions;
using Examine;
using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Cache;
+using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
+using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Web;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
@@ -24,18 +28,27 @@ namespace Umbraco.Cms.Infrastructure.Examine
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IEntityService _entityService;
private readonly IUmbracoTreeSearcherFields _treeSearcherFields;
+ private readonly AppCaches _appCaches;
+ private readonly UmbracoMapper _umbracoMapper;
+ private readonly IPublishedUrlProvider _publishedUrlProvider;
public BackOfficeExamineSearcher(IExamineManager examineManager,
- ILocalizationService languageService,
- IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
- IEntityService entityService,
- IUmbracoTreeSearcherFields treeSearcherFields)
+ ILocalizationService languageService,
+ IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
+ IEntityService entityService,
+ IUmbracoTreeSearcherFields treeSearcherFields,
+ AppCaches appCaches,
+ UmbracoMapper umbracoMapper,
+ IPublishedUrlProvider publishedUrlProvider)
{
_examineManager = examineManager;
_languageService = languageService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_entityService = entityService;
_treeSearcherFields = treeSearcherFields;
+ _appCaches = appCaches;
+ _umbracoMapper = umbracoMapper;
+ _publishedUrlProvider = publishedUrlProvider;
}
public IEnumerable Search(string query, UmbracoEntityTypes entityType, int pageSize, long pageIndex, out long totalFound, string searchFrom = null, bool ignoreUserStartNodes = false)
@@ -71,7 +84,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
type = "media";
fields.AddRange(_treeSearcherFields.GetBackOfficeMediaFields());
var allMediaStartNodes = currentUser != null
- ? currentUser.CalculateMediaStartNodeIds(_entityService)
+ ? currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches)
: Array.Empty();
AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService);
break;
@@ -79,7 +92,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
type = "content";
fields.AddRange(_treeSearcherFields.GetBackOfficeDocumentFields());
var allContentStartNodes = currentUser != null
- ? currentUser.CalculateContentStartNodeIds(_entityService)
+ ? currentUser.CalculateContentStartNodeIds(_entityService, _appCaches)
: Array.Empty();
AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService);
break;
@@ -128,7 +141,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
if (surroundedByQuotes)
{
//strip quotes, escape string, the replace again
- query = query.Trim('\"', '\'');
+ query = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote);
query = Lucene.Net.QueryParsers.QueryParser.Escape(query);
@@ -162,7 +175,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
}
else
{
- var trimmed = query.Trim(new[] { '\"', '\'' });
+ var trimmed = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote);
//nothing to search
if (searchFrom.IsNullOrWhiteSpace() && trimmed.IsNullOrWhiteSpace())
@@ -175,7 +188,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
{
query = Lucene.Net.QueryParsers.QueryParser.Escape(query);
- var querywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ var querywords = query.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries);
sb.Append("+(");
@@ -341,5 +354,85 @@ namespace Umbraco.Cms.Infrastructure.Examine
sb.Append(path);
sb.Append("\\,*");
}
+
+ ///
+ /// Returns a collection of entities for media based on search results
+ ///
+ ///
+ ///
+ private IEnumerable MemberFromSearchResults(IEnumerable results)
+ {
+ //add additional data
+ foreach (var result in results)
+ {
+ var m = _umbracoMapper.Map(result);
+
+ //if no icon could be mapped, it will be set to document, so change it to picture
+ if (m.Icon == Constants.Icons.DefaultIcon)
+ {
+ m.Icon = Constants.Icons.Member;
+ }
+
+ if (result.Values.ContainsKey("email") && result.Values["email"] != null)
+ {
+ m.AdditionalData["Email"] = result.Values["email"];
+ }
+ if (result.Values.ContainsKey(UmbracoExamineFieldNames.NodeKeyFieldName) && result.Values[UmbracoExamineFieldNames.NodeKeyFieldName] != null)
+ {
+ if (Guid.TryParse(result.Values[UmbracoExamineFieldNames.NodeKeyFieldName], out var key))
+ {
+ m.Key = key;
+ }
+ }
+
+ yield return m;
+ }
+ }
+
+ ///
+ /// Returns a collection of entities for media based on search results
+ ///
+ ///
+ ///
+ private IEnumerable MediaFromSearchResults(IEnumerable results)
+ => _umbracoMapper.Map>(results);
+
+ ///
+ /// Returns a collection of entities for content based on search results
+ ///
+ ///
+ ///
+ private IEnumerable ContentFromSearchResults(IEnumerable results, string culture = null)
+ {
+ var defaultLang = _languageService.GetDefaultLanguageIsoCode();
+ foreach (var result in results)
+ {
+ var entity = _umbracoMapper.Map(result, context =>
+ {
+ if (culture != null)
+ {
+ context.SetCulture(culture);
+ }
+ }
+ );
+
+ var intId = entity.Id.TryConvertTo();
+ if (intId.Success)
+ {
+ //if it varies by culture, return the default language URL
+ if (result.Values.TryGetValue(UmbracoExamineFieldNames.VariesByCultureFieldName, out var varies) && varies == "y")
+ {
+ entity.AdditionalData["Url"] = _publishedUrlProvider.GetUrl(intId.Result, culture: culture ?? defaultLang);
+ }
+ else
+ {
+ entity.AdditionalData["Url"] = _publishedUrlProvider.GetUrl(intId.Result);
+ }
+ }
+
+ yield return entity;
+ }
+ }
+
}
}
diff --git a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj
index 43f06ceef3..329c11f879 100644
--- a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj
+++ b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj
@@ -5,6 +5,11 @@
Umbraco.Cms.Infrastructure.Examine
Umbraco CMS
Umbraco.Examine.Lucene
+
+
+ false
+
+ Umbraco.Cms.Examine.Lucene
diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs
index 7f0b33cfc6..ed24221e25 100644
--- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs
+++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs
@@ -43,7 +43,6 @@ namespace Umbraco.Cms.Core.Cache
private static readonly Lazy CandidateHandlers = new Lazy(() =>
{
- var underscore = new[] { '_' };
return typeof(DistributedCacheBinder)
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
@@ -51,7 +50,7 @@ namespace Umbraco.Cms.Core.Cache
{
if (x.Name.Contains("_") == false) return null;
- var parts = x.Name.Split(underscore, StringSplitOptions.RemoveEmptyEntries).Length;
+ var parts = x.Name.Split(Constants.CharArrays.Underscore, StringSplitOptions.RemoveEmptyEntries).Length;
if (parts != 2) return null;
var parameters = x.GetParameters();
diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs
index e28af2f49d..a0ba0ff128 100644
--- a/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs
+++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs
@@ -133,13 +133,13 @@ namespace Umbraco.Extensions
public static void RefreshMemberCache(this DistributedCache dc, params IMember[] members)
{
if (members.Length == 0) return;
- dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username)));
+ dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username, false)));
}
public static void RemoveMemberCache(this DistributedCache dc, params IMember[] members)
{
if (members.Length == 0) return;
- dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username)));
+ dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username, true)));
}
#endregion
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index e97367b804..e3b327ffaa 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -125,8 +125,6 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique();
- builder.Services.AddUnique();
-
// register *all* checks, except those marked [HideFromTypeFinder] of course
builder.Services.AddUnique();
@@ -192,8 +190,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
{
builder.Services.AddUnique(factory =>
{
- var globalSettings = factory.GetRequiredService>().Value;
- var connectionStrings = factory.GetRequiredService>().Value;
+ var globalSettings = factory.GetRequiredService>();
+ var connectionStrings = factory.GetRequiredService>();
var hostingEnvironment = factory.GetRequiredService();
var dbCreator = factory.GetRequiredService();
@@ -201,7 +199,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var loggerFactory = factory.GetRequiredService();
- return globalSettings.MainDomLock.Equals("SqlMainDomLock") || isWindows == false
+ return globalSettings.Value.MainDomLock.Equals("SqlMainDomLock") || isWindows == false
? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment, databaseSchemaCreatorFactory)
: new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment);
});
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs
index 8292fd2ecb..710565500e 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs
@@ -55,6 +55,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
+ builder.Services.AddUnique();
return builder;
}
diff --git a/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs b/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs
index 010ccdf149..463e8dee26 100644
--- a/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs
+++ b/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Examine;
+using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
@@ -13,9 +14,9 @@ namespace Umbraco.Cms.Infrastructure.Examine
public class ContentValueSetValidator : ValueSetValidator, IContentValueSetValidator
{
private readonly IPublicAccessService _publicAccessService;
-
+ private readonly IScopeProvider _scopeProvider;
private const string PathKey = "path";
- private static readonly IEnumerable ValidCategories = new[] {IndexTypes.Content, IndexTypes.Media};
+ private static readonly IEnumerable ValidCategories = new[] { IndexTypes.Content, IndexTypes.Media };
protected override IEnumerable ValidIndexCategories => ValidCategories;
public bool PublishedValuesOnly { get; }
@@ -51,25 +52,38 @@ namespace Umbraco.Cms.Infrastructure.Examine
public bool ValidateProtectedContent(string path, string category)
{
- if (category == IndexTypes.Content
- && !SupportProtectedContent
- //if the service is null we can't look this up so we'll return false
- && (_publicAccessService == null || _publicAccessService.IsProtected(path)))
+ if (category == IndexTypes.Content && !SupportProtectedContent)
{
- return false;
+ //if the service is null we can't look this up so we'll return false
+ if (_publicAccessService == null || _scopeProvider == null)
+ {
+ return false;
+ }
+
+ // explicit scope since we may be in a background thread
+ using (_scopeProvider.CreateScope(autoComplete: true))
+ {
+ if (_publicAccessService.IsProtected(path))
+ {
+ return false;
+ }
+ }
}
return true;
}
+ // used for tests
public ContentValueSetValidator(bool publishedValuesOnly, int? parentId = null,
IEnumerable includeItemTypes = null, IEnumerable excludeItemTypes = null)
- : this(publishedValuesOnly, true, null, parentId, includeItemTypes, excludeItemTypes)
+ : this(publishedValuesOnly, true, null, null, parentId, includeItemTypes, excludeItemTypes)
{
}
public ContentValueSetValidator(bool publishedValuesOnly, bool supportProtectedContent,
- IPublicAccessService publicAccessService, int? parentId = null,
+ IPublicAccessService publicAccessService,
+ IScopeProvider scopeProvider,
+ int? parentId = null,
IEnumerable includeItemTypes = null, IEnumerable excludeItemTypes = null)
: base(includeItemTypes, excludeItemTypes, null, null)
{
@@ -77,6 +91,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
SupportProtectedContent = supportProtectedContent;
ParentId = parentId;
_publicAccessService = publicAccessService;
+ _scopeProvider = scopeProvider;
}
public override ValueSetValidationResult Validate(ValueSet valueSet)
@@ -101,7 +116,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
&& variesByCulture.Count > 0 && variesByCulture[0].Equals("y"))
{
//so this valueset is for a content that varies by culture, now check for non-published cultures and remove those values
- foreach(var publishField in valueSet.Values.Where(x => x.Key.StartsWith($"{UmbracoExamineFieldNames.PublishedFieldName}_")).ToList())
+ foreach (var publishField in valueSet.Values.Where(x => x.Key.StartsWith($"{UmbracoExamineFieldNames.PublishedFieldName}_")).ToList())
{
if (publishField.Value.Count <= 0 || !publishField.Value[0].Equals("y"))
{
@@ -132,7 +147,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
|| !ValidateProtectedContent(path, valueSet.Category))
return ValueSetValidationResult.Filtered;
- return isFiltered ? ValueSetValidationResult.Filtered: ValueSetValidationResult.Valid;
+ return isFiltered ? ValueSetValidationResult.Filtered : ValueSetValidationResult.Valid;
}
}
}
diff --git a/src/Umbraco.Infrastructure/Examine/UmbracoIndexConfig.cs b/src/Umbraco.Infrastructure/Examine/UmbracoIndexConfig.cs
index 2c282a1924..49607b5851 100644
--- a/src/Umbraco.Infrastructure/Examine/UmbracoIndexConfig.cs
+++ b/src/Umbraco.Infrastructure/Examine/UmbracoIndexConfig.cs
@@ -1,24 +1,28 @@
using Examine;
+using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Infrastructure.Examine
{
public class UmbracoIndexConfig : IUmbracoIndexConfig
{
- public UmbracoIndexConfig(IPublicAccessService publicAccessService)
+
+ public UmbracoIndexConfig(IPublicAccessService publicAccessService, IScopeProvider scopeProvider)
{
+ ScopeProvider = scopeProvider;
PublicAccessService = publicAccessService;
}
protected IPublicAccessService PublicAccessService { get; }
+ protected IScopeProvider ScopeProvider { get; }
public IContentValueSetValidator GetContentValueSetValidator()
{
- return new ContentValueSetValidator(false, true, PublicAccessService);
+ return new ContentValueSetValidator(false, true, PublicAccessService, ScopeProvider);
}
public IContentValueSetValidator GetPublishedContentValueSetValidator()
{
- return new ContentValueSetValidator(true, false, PublicAccessService);
+ return new ContentValueSetValidator(true, false, PublicAccessService, ScopeProvider);
}
///
diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
index ad64319f5e..c9b4aeec82 100644
--- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
@@ -6,6 +6,7 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Logging;
@@ -96,7 +97,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
return;
}
- keepAlivePingUrl = keepAlivePingUrl.Replace("{umbracoApplicationUrl}", umbracoAppUrl.TrimEnd('/'));
+ keepAlivePingUrl = keepAlivePingUrl.Replace("{umbracoApplicationUrl}", umbracoAppUrl.TrimEnd(Constants.CharArrays.ForwardSlash));
}
var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl);
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs
index 13b295f4bc..a758b08081 100644
--- a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs
+++ b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs
@@ -1,85 +1,45 @@
using System.Collections.Generic;
-using System.IO;
using System.Linq;
-using Newtonsoft.Json;
-using Umbraco.Cms.Core.Hosting;
-using Umbraco.Cms.Core.Routing;
-using Formatting = Newtonsoft.Json.Formatting;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Persistence.Repositories;
+using Umbraco.Cms.Core.Scoping;
namespace Umbraco.Cms.Core.Logging.Viewer
{
public class LogViewerConfig : ILogViewerConfig
{
- private readonly IHostingEnvironment _hostingEnvironment;
- private static readonly string _pathToSearches = WebPath.Combine(Cms.Core.Constants.SystemDirectories.Config, "logviewer.searches.config.js");
- private readonly FileInfo _searchesConfig;
+ private readonly ILogViewerQueryRepository _logViewerQueryRepository;
+ private readonly IScopeProvider _scopeProvider;
- public LogViewerConfig(IHostingEnvironment hostingEnvironment)
+ public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, IScopeProvider scopeProvider)
{
- _hostingEnvironment = hostingEnvironment;
- var trimmedPath = _pathToSearches.TrimStart('~', '/').Replace('/', Path.DirectorySeparatorChar);
- var absolutePath = Path.Combine(_hostingEnvironment.ApplicationPhysicalPath, trimmedPath);
- _searchesConfig = new FileInfo(absolutePath);
+ _logViewerQueryRepository = logViewerQueryRepository;
+ _scopeProvider = scopeProvider;
}
public IReadOnlyList GetSavedSearches()
{
- //Our default implementation
-
- //If file does not exist - lets create it with an empty array
- EnsureFileExists();
-
- var rawJson = System.IO.File.ReadAllText(_searchesConfig.FullName);
- return JsonConvert.DeserializeObject(rawJson);
+ using var scope = _scopeProvider.CreateScope(autoComplete: true);
+ var logViewerQueries = _logViewerQueryRepository.GetMany();
+ var result = logViewerQueries.Select(x => new SavedLogSearch() { Name = x.Name, Query = x.Query }).ToArray();
+ return result;
}
public IReadOnlyList AddSavedSearch(string name, string query)
{
- //Get the existing items
- var searches = GetSavedSearches().ToList();
+ using var scope = _scopeProvider.CreateScope(autoComplete: true);
+ _logViewerQueryRepository.Save(new LogViewerQuery(name, query));
- //Add the new item to the bottom of the list
- searches.Add(new SavedLogSearch { Name = name, Query = query });
-
- //Serialize to JSON string
- var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
-
- //If file does not exist - lets create it with an empty array
- EnsureFileExists();
-
- //Write it back down to file
- System.IO.File.WriteAllText(_searchesConfig.FullName, rawJson);
-
- //Return the updated object - so we can instantly reset the entire array from the API response
- //As opposed to push a new item into the array
- return searches;
+ return GetSavedSearches();
}
public IReadOnlyList DeleteSavedSearch(string name, string query)
{
- //Get the existing items
- var searches = GetSavedSearches().ToList();
-
- //Removes the search
- searches.RemoveAll(s => s.Name.Equals(name) && s.Query.Equals(query));
-
- //Serialize to JSON string
- var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
-
- //Write it back down to file
- System.IO.File.WriteAllText(_searchesConfig.FullName, rawJson);
-
+ using var scope = _scopeProvider.CreateScope(autoComplete: true);
+ var item = _logViewerQueryRepository.GetByName(name);
+ _logViewerQueryRepository.Delete(item);
//Return the updated object - so we can instantly reset the entire array from the API response
- return searches;
- }
-
- private void EnsureFileExists()
- {
- if (_searchesConfig.Exists) return;
- using (var writer = _searchesConfig.CreateText())
- {
- writer.Write("[]");
- }
+ return GetSavedSearches();
}
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
index 72e8b864bf..30759ae789 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
@@ -1,9 +1,11 @@
using System;
+using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
+using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Extensions;
@@ -76,6 +78,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.KeyValue))
CreateKeyValueData();
+ if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery))
+ CreateLogViewerQueryData();
+
_logger.LogInformation("Done creating table {TableName} data.", tableName);
}
@@ -345,5 +350,17 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, UpdateDate = DateTime.Now });
}
+
+ private void CreateLogViewerQueryData()
+ {
+ var defaultData = MigrateLogViewerQueriesFromFileToDb.DefaultLogQueries.ToArray();
+
+ for (int i = 0; i < defaultData.Length; i++)
+ {
+ var dto = defaultData[i];
+ dto.Id = i+1;
+ _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery, "id", false, dto);
+ }
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
index d7db160b56..d4ce35aebc 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
@@ -91,7 +91,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
typeof (AuditEntryDto),
typeof (ContentVersionCultureVariationDto),
typeof (DocumentCultureVariationDto),
- typeof (ContentScheduleDto)
+ typeof (ContentScheduleDto),
+ typeof (LogViewerQueryDto)
};
///
diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/DeleteLogViewerQueryFile.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/DeleteLogViewerQueryFile.cs
new file mode 100644
index 0000000000..cc1828dc2e
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/PostMigrations/DeleteLogViewerQueryFile.cs
@@ -0,0 +1,34 @@
+using System.IO;
+using Umbraco.Cms.Core.Hosting;
+using Umbraco.Cms.Core.Migrations;
+using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.PostMigrations
+{
+ ///
+ /// Deletes the old file that saved log queries
+ ///
+ public class DeleteLogViewerQueryFile : IMigration
+ {
+ private readonly IHostingEnvironment _hostingEnvironment;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DeleteLogViewerQueryFile(IMigrationContext context, IHostingEnvironment hostingEnvironment)
+ {
+ _hostingEnvironment = hostingEnvironment;
+ }
+
+ ///
+ public void Migrate()
+ {
+ var logViewerQueryFile = MigrateLogViewerQueriesFromFileToDb.GetLogViewerQueryFile(_hostingEnvironment);
+
+ if(File.Exists(logViewerQueryFile))
+ {
+ File.Delete(logViewerQueryFile);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
index 9aacab1740..64d704bb11 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
@@ -9,6 +9,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_10_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_6_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_7_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_9_0;
+using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
@@ -197,6 +198,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
To("{B5838FF5-1D22-4F6C-BCEB-F83ACB14B575}");
// to 8.10.0
To("{D6A8D863-38EC-44FB-91EC-ACD6A668BD18}");
+
+ // to 8.10.0
+ To("{22D801BA-A1FF-4539-BFCC-2139B55594F8}");
//FINAL
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs
index 68ad810619..b5e50d2248 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
@@ -31,7 +32,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0
protected int[] ConvertStringValues(string val)
{
- var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ var splitVals = val.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
var intVals = splitVals
.Select(x => int.TryParse(x, out var i) ? i : int.MinValue)
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs
new file mode 100644
index 0000000000..88ffe1a66c
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs
@@ -0,0 +1,108 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using Newtonsoft.Json;
+using Umbraco.Cms.Core.Hosting;
+using Umbraco.Cms.Infrastructure.Migrations.PostMigrations;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
+{
+
+ public class MigrateLogViewerQueriesFromFileToDb : MigrationBase
+ {
+ private readonly IHostingEnvironment _hostingEnvironment;
+ internal static readonly IEnumerable DefaultLogQueries = new LogViewerQueryDto[]
+ {
+ new (){
+ Name = "Find all logs where the Level is NOT Verbose and NOT Debug",
+ Query = "Not(@Level='Verbose') and Not(@Level='Debug')"
+ },
+ new (){
+ Name = "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)",
+ Query = "Has(@Exception)"
+ },
+ new (){
+ Name = "Find all logs that have the property 'Duration'",
+ Query = "Has(Duration)"
+ },
+ new (){
+ Name = "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
+ Query = "Has(Duration) and Duration > 1000"
+ },
+ new (){
+ Name = "Find all logs that are from the namespace 'Umbraco.Core'",
+ Query = "StartsWith(SourceContext, 'Umbraco.Core')"
+ },
+ new (){
+ Name = "Find all logs that use a specific log message template",
+ Query = "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'"
+ },
+ new (){
+ Name = "Find logs where one of the items in the SortedComponentTypes property array is equal to",
+ Query = "SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'"
+ },
+ new (){
+ Name = "Find logs where one of the items in the SortedComponentTypes property array contains",
+ Query = "Contains(SortedComponentTypes[?], 'DatabaseServer')"
+ },
+ new (){
+ Name = "Find all logs that the message has localhost in it with SQL like",
+ Query = "@Message like '%localhost%'"
+ },
+ new (){
+ Name = "Find all logs that the message that starts with 'end' in it with SQL like",
+ Query = "@Message like 'end%'"
+ }
+ };
+
+ public MigrateLogViewerQueriesFromFileToDb(IMigrationContext context, IHostingEnvironment hostingEnvironment)
+ : base(context)
+ {
+ _hostingEnvironment = hostingEnvironment;
+ }
+
+ public override void Migrate()
+ {
+ Debugger.Launch();
+ Debugger.Break();
+ CreateDatabaseTable();
+ MigrateFileContentToDB();
+ }
+ private void CreateDatabaseTable()
+ {
+ var tables = SqlSyntax.GetTablesInSchema(Context.Database);
+ if (!tables.InvariantContains(Core.Constants.DatabaseSchema.Tables.LogViewerQuery))
+ {
+ Create.Table().Do();
+ }
+ }
+
+ internal static string GetLogViewerQueryFile(IHostingEnvironment hostingEnvironment)
+ {
+ return hostingEnvironment.MapPathContentRoot(
+ Path.Combine(Cms.Core.Constants.SystemDirectories.Config, "logviewer.searches.config.js"));
+ }
+ private void MigrateFileContentToDB()
+ {
+ var logViewerQueryFile = GetLogViewerQueryFile(_hostingEnvironment);
+
+ var logQueriesInFile = File.Exists(logViewerQueryFile) ?
+ JsonConvert.DeserializeObject(File.ReadAllText(logViewerQueryFile))
+ : DefaultLogQueries;
+
+ var logQueriesInDb = Database.Query().ToArray();
+
+ if (logQueriesInDb.Any())
+ {
+ return;
+ }
+
+ Database.InsertBulk(logQueriesInFile);
+
+ Context.AddPostMigration();
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Models/PathValidationExtensions.cs b/src/Umbraco.Infrastructure/Models/PathValidationExtensions.cs
index d805eba9d5..8758d17d07 100644
--- a/src/Umbraco.Infrastructure/Models/PathValidationExtensions.cs
+++ b/src/Umbraco.Infrastructure/Models/PathValidationExtensions.cs
@@ -27,7 +27,7 @@ namespace Umbraco.Cms.Core.Models
if (entity.Path.IsNullOrWhiteSpace())
throw new InvalidDataException($"The content item {entity.NodeId} has an empty path: {entity.Path} with parentID: {entity.ParentId}");
- var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
if (pathParts.Length < 2)
{
//a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id
@@ -55,7 +55,7 @@ namespace Umbraco.Cms.Core.Models
if (entity.Path.IsNullOrWhiteSpace())
return false;
- var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
if (pathParts.Length < 2)
{
//a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id
diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs
index 7acfbcf26a..40a3aaf9f2 100644
--- a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs
+++ b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
@@ -447,7 +448,7 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
{
WriteNonGenericClrType(sb, type.Substring(0, p));
sb.Append("<");
- var args = type.Substring(p + 1).TrimEnd('>').Split(','); // fixme will NOT work with nested generic types
+ var args = type.Substring(p + 1).TrimEnd(Constants.CharArrays.GreaterThan).Split(Constants.CharArrays.Comma); // fixme will NOT work with nested generic types
for (var i = 0; i < args.Length; i++)
{
if (i > 0) sb.Append(", ");
diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
index ead0791f08..16007069c6 100644
--- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
+++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
@@ -439,12 +439,28 @@ namespace Umbraco.Cms.Core.Packaging
int.Parse(sortOrder),
template?.Id);
+ // Handle culture specific node names
+ const string nodeNamePrefix = "nodeName-";
+ // Get the installed culture iso names, we create a localized content node with a culture that does not exist in the project
+ // We have to use Invariant comparisons, because when we get them from ContentBase in EntityXmlSerializer they're all lowercase.
+ var installedLanguages = _localizationService.GetAllLanguages().Select(l => l.IsoCode).ToArray();
+ foreach (var localizedNodeName in element.Attributes().Where(a => a.Name.LocalName.InvariantStartsWith(nodeNamePrefix)))
+ {
+ var newCulture = localizedNodeName.Name.LocalName.Substring(nodeNamePrefix.Length);
+ // Skip the culture if it does not exist in the current project
+ if (installedLanguages.InvariantContains(newCulture))
+ {
+ content.SetCultureName(localizedNodeName.Value, newCulture);
+ }
+ }
+
//Here we make sure that we take composition properties in account as well
//otherwise we would skip them and end up losing content
var propTypes = contentType.CompositionPropertyTypes.Any()
? contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x)
: contentType.PropertyTypes.ToDictionary(x => x.Alias, x => x);
+ var foundLanguages = new HashSet();
foreach (var property in properties)
{
string propertyTypeAlias = property.Name.LocalName;
@@ -452,14 +468,30 @@ namespace Umbraco.Cms.Core.Packaging
{
var propertyValue = property.Value;
+ // Handle properties language attributes
+ var propertyLang = property.Attribute(XName.Get("lang"))?.Value;
+ foundLanguages.Add(propertyLang);
if (propTypes.TryGetValue(propertyTypeAlias, out var propertyType))
{
- //set property value
- content.SetValue(propertyTypeAlias, propertyValue);
+ // set property value
+ // Skip unsupported language variation, otherwise we'll get a "not supported error"
+ // We allow null, because that's invariant
+ if (installedLanguages.InvariantContains(propertyLang) || propertyLang is null)
+ {
+ content.SetValue(propertyTypeAlias, propertyValue, propertyLang);
+ }
}
}
}
+ foreach (var propertyLang in foundLanguages)
+ {
+ if (string.IsNullOrEmpty(content.GetCultureName(propertyLang)) && installedLanguages.InvariantContains(propertyLang))
+ {
+ content.SetCultureName(nodeName, propertyLang);
+ }
+ }
+
return content;
}
@@ -635,7 +667,7 @@ namespace Umbraco.Cms.Core.Packaging
&& ((string)infoElement.Element("Master")).IsNullOrWhiteSpace())
{
var alias = documentType.Element("Info").Element("Alias").Value;
- var folders = foldersAttribute.Value.Split('/');
+ var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash);
var rootFolder = WebUtility.UrlDecode(folders[0]);
//level 1 = root level folders, there can only be one with the same name
var current = _contentTypeService.GetContainers(rootFolder, 1).FirstOrDefault();
@@ -1095,7 +1127,7 @@ namespace Umbraco.Cms.Core.Packaging
if (foldersAttribute != null)
{
var name = datatypeElement.Attribute("Name").Value;
- var folders = foldersAttribute.Value.Split('/');
+ var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash);
var rootFolder = WebUtility.UrlDecode(folders[0]);
//there will only be a single result by name for level 1 (root) containers
var current = _dataTypeService.GetContainers(rootFolder, 1).FirstOrDefault();
diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs
index 986e2d760a..a3ca285918 100644
--- a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs
@@ -161,7 +161,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions
if (string.IsNullOrEmpty(attribute.ForColumns) == false)
{
- var columns = attribute.ForColumns.Split(',').Select(p => p.Trim());
+ var columns = attribute.ForColumns.Split(Constants.CharArrays.Comma).Select(p => p.Trim());
foreach (var column in columns)
{
definition.Columns.Add(new IndexColumnDefinition {Name = column, Direction = Direction.Ascending});
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/LogViewerQueryDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/LogViewerQueryDto.cs
new file mode 100644
index 0000000000..71642c8b73
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/LogViewerQueryDto.cs
@@ -0,0 +1,22 @@
+using NPoco;
+using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
+
+namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
+{
+ [TableName(Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery)]
+ [PrimaryKey("id")]
+ [ExplicitColumns]
+ internal class LogViewerQueryDto
+ {
+ [Column("id")]
+ [PrimaryKeyColumn]
+ public int Id { get; set; }
+
+ [Column("name")]
+ [Index(IndexTypes.UniqueNonClustered, Name = "IX_LogViewerQuery_name")]
+ public string Name { get; set; }
+
+ [Column("query")]
+ public string Query { get; set; }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/LogViewerQueryMapper.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/LogViewerQueryMapper.cs
new file mode 100644
index 0000000000..807e3b6c02
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/Mappers/LogViewerQueryMapper.cs
@@ -0,0 +1,22 @@
+using System;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+
+namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
+{
+ [MapperFor(typeof(ILogViewerQuery))]
+ [MapperFor(typeof(LogViewerQuery))]
+ public sealed class LogViewerQueryMapper : BaseMapper
+ {
+ public LogViewerQueryMapper(Lazy sqlContext, MapperConfigurationStore maps)
+ : base(sqlContext, maps)
+ { }
+
+ protected override void DefineMaps()
+ {
+ DefineMap(nameof(ILogViewerQuery.Id), nameof(LogViewerQueryDto.Id));
+ DefineMap(nameof(ILogViewerQuery.Name), nameof(LogViewerQueryDto.Name));
+ DefineMap(nameof(ILogViewerQuery.Query), nameof(LogViewerQueryDto.Query));
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/MapperCollectionBuilder.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/MapperCollectionBuilder.cs
index c5f0814469..38f1176b75 100644
--- a/src/Umbraco.Infrastructure/Persistence/Mappers/MapperCollectionBuilder.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Mappers/MapperCollectionBuilder.cs
@@ -53,6 +53,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
Add();
Add();
Add();
+ Add();
return this;
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index d3c28b60de..da96e6dfc8 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -525,7 +525,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
currentParentIds.Add(node.NodeId);
// paths parts without the roots
- var pathParts = node.Path.Split(',').Where(x => !rootIds.Contains(int.Parse(x))).ToArray();
+ var pathParts = node.Path.Split(Constants.CharArrays.Comma).Where(x => !rootIds.Contains(int.Parse(x))).ToArray();
if (!prevParentIds.Contains(node.ParentId))
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index ee2e3aee60..7c8d816ec8 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -5,6 +5,7 @@ using System.Globalization;
using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Exceptions;
@@ -1339,7 +1340,7 @@ WHERE cmsContentType." + aliasColumn + @" LIKE @pattern",
///
public bool HasContainerInPath(string contentPath)
{
- var ids = contentPath.Split(',').Select(int.Parse).ToArray();
+ var ids = contentPath.Split(Constants.CharArrays.Comma).Select(int.Parse).ToArray();
return HasContainerInPath(ids);
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
index 43ee275e7f..3500f0458d 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
@@ -916,7 +917,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
if (content.ParentId == -1)
return content.Published;
- var ids = content.Path.Split(',').Skip(1).Select(int.Parse);
+ var ids = content.Path.Split(Constants.CharArrays.Comma).Skip(1).Select(int.Parse);
var sql = SqlContext.Sql()
.SelectCount(x => x.NodeId)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs
new file mode 100644
index 0000000000..35f0b8fdab
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using NPoco;
+using Umbraco.Cms.Core.Cache;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Persistence.Querying;
+using Umbraco.Cms.Core.Persistence.Repositories;
+using Umbraco.Cms.Core.Scoping;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
+{
+ internal class LogViewerQueryRepository : EntityRepositoryBase, ILogViewerQueryRepository
+ {
+ public LogViewerQueryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
+ : base(scopeAccessor, cache, logger)
+ { }
+
+ protected override IRepositoryCachePolicy CreateCachePolicy()
+ {
+ return new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false);
+ }
+
+ protected override IEnumerable PerformGetAll(params int[] ids)
+ {
+ var sql = GetBaseQuery(false).Where($"{Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery}.id > 0");
+ if (ids.Any())
+ {
+ sql.Where($"{Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery}.id in (@ids)", new { ids = ids });
+ }
+
+ return Database.Fetch(sql).Select(ConvertFromDto);
+ }
+
+ protected override IEnumerable PerformGetByQuery(IQuery query)
+ {
+ throw new NotSupportedException("This repository does not support this method");
+ }
+
+ protected override Sql GetBaseQuery(bool isCount)
+ {
+ var sql = Sql();
+ sql = isCount ? sql.SelectCount() : sql.Select();
+ sql = sql.From();
+ return sql;
+ }
+
+ protected override string GetBaseWhereClause()
+ {
+ return $"{Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery}.id = @id";
+ }
+
+ protected override IEnumerable GetDeleteClauses()
+ {
+ var list = new List
+ {
+ $"DELETE FROM {Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery} WHERE id = @id"
+ };
+ return list;
+ }
+
+ protected override Guid NodeObjectTypeId
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ protected override void PersistNewItem(ILogViewerQuery entity)
+ {
+ var exists = Database.ExecuteScalar($"SELECT COUNT(*) FROM {Core.Constants.DatabaseSchema.Tables.LogViewerQuery} WHERE name = @name",
+ new { name = entity.Name });
+ if (exists > 0) throw new DuplicateNameException($"The log query name '{entity.Name}' is already used");
+
+ entity.AddingEntity();
+
+ var factory = new LogViewerQueryModelFactory();
+ var dto = factory.BuildDto(entity);
+
+ var id = Convert.ToInt32(Database.Insert(dto));
+ entity.Id = id;
+ }
+
+ protected override void PersistUpdatedItem(ILogViewerQuery entity)
+ {
+ entity.UpdatingEntity();
+
+ var exists = Database.ExecuteScalar($"SELECT COUNT(*) FROM {Core.Constants.DatabaseSchema.Tables.LogViewerQuery} WHERE name = @name AND id <> @id",
+ new { name = entity.Name, id = entity.Id });
+ //ensure there is no other log query with the same name on another entity
+ if (exists > 0) throw new DuplicateNameException($"The log query name '{entity.Name}' is already used");
+
+
+ var factory = new LogViewerQueryModelFactory();
+ var dto = factory.BuildDto(entity);
+
+ Database.Update(dto);
+ }
+
+ private ILogViewerQuery ConvertFromDto(LogViewerQueryDto dto)
+ {
+ var factory = new LogViewerQueryModelFactory();
+ var entity = factory.BuildEntity(dto);
+ return entity;
+ }
+
+ internal class LogViewerQueryModelFactory
+ {
+
+ public ILogViewerQuery BuildEntity(LogViewerQueryDto dto)
+ {
+ var logViewerQuery = new LogViewerQuery(dto.Name, dto.Query)
+ {
+ Id = dto.Id,
+ };
+ return logViewerQuery;
+ }
+
+ public LogViewerQueryDto BuildDto(ILogViewerQuery entity)
+ {
+ var dto = new LogViewerQueryDto { Name = entity.Name, Query = entity.Query, Id = entity.Id };
+ return dto;
+ }
+ }
+
+ protected override ILogViewerQuery PerformGet(int id)
+ {
+ //use the underlying GetAll which will force cache all log queries
+ return GetMany().FirstOrDefault(x => x.Id == id);
+ }
+
+ public ILogViewerQuery GetByName(string name)
+ {
+ //use the underlying GetAll which will force cache all log queries
+ return GetMany().FirstOrDefault(x => x.Name == name);
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
index 80201360c3..4e4f34bf54 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
@@ -213,7 +213,7 @@ ORDER BY colName";
return false;
//now detect if there's been a timeout
- if (DateTime.UtcNow - found.LastValidatedUtc > TimeSpan.FromMinutes(_globalSettings.TimeOutInMinutes))
+ if (DateTime.UtcNow - found.LastValidatedUtc > _globalSettings.TimeOut)
{
//timeout detected, update the record
ClearLoginSession(sessionId);
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs b/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs
index a036321c38..789331177e 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs
@@ -1,5 +1,7 @@
using System;
using System.Data.Common;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
namespace Umbraco.Cms.Infrastructure.Persistence
@@ -7,10 +9,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public class SqlServerDbProviderFactoryCreator : IDbProviderFactoryCreator
{
private readonly Func _getFactory;
+ private readonly IOptions _globalSettings;
- public SqlServerDbProviderFactoryCreator(Func getFactory)
+ public SqlServerDbProviderFactoryCreator(Func getFactory, IOptions globalSettings)
{
_getFactory = getFactory;
+ _globalSettings = globalSettings;
}
public DbProviderFactory CreateFactory(string providerName)
@@ -25,7 +29,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
return providerName switch
{
Cms.Core.Constants.DbProviderNames.SqlCe => throw new NotSupportedException("SqlCe is not supported"),
- Cms.Core.Constants.DbProviderNames.SqlServer => new SqlServerSyntaxProvider(),
+ Cms.Core.Constants.DbProviderNames.SqlServer => new SqlServerSyntaxProvider(_globalSettings),
_ => throw new InvalidOperationException($"Unknown provider name \"{providerName}\""),
};
}
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
index 37038255a0..6c551648b7 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
@@ -131,6 +131,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
///
bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
+ void ReadLock(IDatabase db, TimeSpan timeout, int lockId);
+ void WriteLock(IDatabase db, TimeSpan timeout, int lockId);
+
void ReadLock(IDatabase db, params int[] lockIds);
void WriteLock(IDatabase db, params int[] lockIds);
}
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs
index 8a80a33ad0..4c75128926 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs
@@ -1,6 +1,7 @@
using System;
using System.Data;
using System.Linq;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Persistence.Querying;
namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
@@ -34,7 +35,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
if (tableName.Contains(".") == false)
return $"[{tableName}]";
- var tableNameParts = tableName.Split(new[] { '.' }, 2);
+ var tableNameParts = tableName.Split(Constants.CharArrays.Period, 2);
return $"[{tableNameParts[0]}].[{tableNameParts[1]}]";
}
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
index 279ab1215f..0270f77904 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
@@ -1,10 +1,13 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using NPoco;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Extensions;
@@ -15,6 +18,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
///
public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase
{
+ private readonly IOptions _globalSettings;
+
+ public SqlServerSyntaxProvider(IOptions globalSettings)
+ {
+ _globalSettings = globalSettings;
+ }
+
public override string ProviderName => Cms.Core.Constants.DatabaseProviders.SqlServer;
public ServerVersionInfo ServerVersion { get; private set; }
@@ -76,7 +86,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
private static VersionName MapProductVersion(string productVersion)
{
- var firstPart = string.IsNullOrWhiteSpace(productVersion) ? "??" : productVersion.Split('.')[0];
+ var firstPart = string.IsNullOrWhiteSpace(productVersion) ? "??" : productVersion.Split(Constants.CharArrays.Period)[0];
switch (firstPart)
{
case "??":
@@ -256,9 +266,19 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
return result > 0;
}
+ public override void WriteLock(IDatabase db, TimeSpan timeout, int lockId)
+ {
+ // soon as we get Database, a transaction is started
+
+ if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
+ throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
+
+ ObtainWriteLock(db, timeout, lockId);
+ }
+
public override void WriteLock(IDatabase db, params int[] lockIds)
{
- WriteLock(db, TimeSpan.FromSeconds(5), lockIds);
+ WriteLock(db, _globalSettings.Value.SqlWriteLockTimeOut, lockIds);
}
public void WriteLock(IDatabase db, TimeSpan timeout, params int[] lockIds)
@@ -280,19 +300,33 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
}
-
- // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
- db.Execute($"SET LOCK_TIMEOUT {timeout.TotalMilliseconds};");
- var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
- if (i == 0) // ensure we are actually locking!
- {
- throw new ArgumentException($"LockObject with id={lockId} does not exist.");
- }
+ ObtainWriteLock(db, timeout, lockId);
}
}
+ private static void ObtainWriteLock(IDatabase db, TimeSpan timeout, int lockId)
+ {
+ db.Execute("SET LOCK_TIMEOUT " + timeout.TotalMilliseconds + ";");
+ var i = db.Execute(
+ @"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
+ new {id = lockId});
+ if (i == 0) // ensure we are actually locking!
+ {
+ throw new ArgumentException($"LockObject with id={lockId} does not exist.");
+ }
+ }
+
+ public override void ReadLock(IDatabase db, TimeSpan timeout, int lockId)
+ {
+ // soon as we get Database, a transaction is started
+
+ if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
+ throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
+
+ ObtainReadLock(db, timeout, lockId);
+ }
public override void ReadLock(IDatabase db, params int[] lockIds)
{
@@ -313,14 +347,23 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
}
- // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
- var i = db.ExecuteScalar("SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id", new { id = lockId });
- if (i == null) // ensure we are actually locking!
- {
- throw new ArgumentException($"LockObject with id={lockId} does not exist.", nameof(lockIds));
- }
+ ObtainReadLock(db, null, lockId);
+ }
+ }
+
+ private static void ObtainReadLock(IDatabase db, TimeSpan? timeout, int lockId)
+ {
+ if (timeout.HasValue)
+ {
+ db.Execute(@"SET LOCK_TIMEOUT " + timeout.Value.TotalMilliseconds + ";");
+ }
+
+ var i = db.ExecuteScalar("SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id", new {id = lockId});
+ if (i == null) // ensure we are actually locking!
+ {
+ throw new ArgumentException($"LockObject with id={lockId} does not exist.", nameof(lockId));
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
index f926a9d60f..b0afa9d75b 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NPoco;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.Querying;
@@ -239,6 +240,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
public abstract void ReadLock(IDatabase db, params int[] lockIds);
public abstract void WriteLock(IDatabase db, params int[] lockIds);
+ public abstract void ReadLock(IDatabase db, TimeSpan timeout, int lockId);
+
+ public abstract void WriteLock(IDatabase db, TimeSpan timeout, int lockId);
public virtual bool DoesTableExist(IDatabase db, string tableName)
{
@@ -407,7 +411,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
var columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns)
? GetQuotedColumnName(columnDefinition.Name)
: string.Join(", ", columnDefinition.PrimaryKeyColumns
- .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
+ .Split(Constants.CharArrays.CommaSpace, StringSplitOptions.RemoveEmptyEntries)
.Select(GetQuotedColumnName));
var primaryKeyPart = string.Concat("PRIMARY KEY", columnDefinition.IsIndexed ? " CLUSTERED" : " NONCLUSTERED");
diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs
index 581517326f..944195cb82 100644
--- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs
@@ -31,7 +31,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
{
private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
- private readonly GlobalSettings _globalSettings;
+ private readonly IOptions _globalSettings;
private readonly Lazy _mappers;
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
@@ -70,21 +70,18 @@ namespace Umbraco.Cms.Infrastructure.Persistence
#region Constructors
- ///
- /// Initializes a new instance of the .
- ///
- /// Used by core runtime.
- public UmbracoDatabaseFactory(ILogger logger, ILoggerFactory loggerFactory, IOptions globalSettings, IOptions connectionStrings, Lazy mappers,IDbProviderFactoryCreator dbProviderFactoryCreator, DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
- : this(logger, loggerFactory, globalSettings.Value, connectionStrings.Value, mappers, dbProviderFactoryCreator, databaseSchemaCreatorFactory)
- {
-
- }
-
///
/// Initializes a new instance of the .
///
/// Used by the other ctor and in tests.
- public UmbracoDatabaseFactory(ILogger logger, ILoggerFactory loggerFactory, GlobalSettings globalSettings, ConnectionStrings connectionStrings, Lazy mappers, IDbProviderFactoryCreator dbProviderFactoryCreator, DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
+ public UmbracoDatabaseFactory(
+ ILogger logger,
+ ILoggerFactory loggerFactory,
+ IOptions globalSettings,
+ IOptions connectionStrings,
+ Lazy mappers,
+ IDbProviderFactoryCreator dbProviderFactoryCreator,
+ DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
{
_globalSettings = globalSettings;
@@ -94,7 +91,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_loggerFactory = loggerFactory;
- var settings = connectionStrings.UmbracoConnectionString;
+ var settings = connectionStrings.Value.UmbracoConnectionString;
if (settings == null)
{
@@ -166,7 +163,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
{
// replace NPoco database type by a more efficient one
- var setting = _globalSettings.DatabaseFactoryServerVersion;
+ var setting = _globalSettings.Value.DatabaseFactoryServerVersion;
var fromSettings = false;
if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.")
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs
index a40ab92ca8..6e732cdc0f 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs
@@ -202,7 +202,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
_richTextPropertyValueEditor.GetReferences(x.Value)))
yield return umbracoEntityReference;
- foreach (var umbracoEntityReference in mediaValues.SelectMany(x =>
+ foreach (var umbracoEntityReference in mediaValues.Where(x=>x.Value.HasValues).SelectMany(x =>
_mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"])))
yield return umbracoEntityReference;
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs
index 02b268682d..f1e6a16bd4 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs
@@ -143,6 +143,10 @@ namespace Umbraco.Cms.Core.PropertyEditors
return base.ToEditor(property, culture, segment);
}
+ private static readonly JsonSerializerSettings LinkDisplayJsonSerializerSettings = new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Ignore
+ };
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
{
@@ -164,11 +168,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
Target = link.Target,
Udi = link.Udi,
Url = link.Udi == null ? link.Url : null, // only save the URL for external links
- },
- new JsonSerializerSettings
- {
- NullValueHandling = NullValueHandling.Ignore
- });
+ }, LinkDisplayJsonSerializerSettings
+ );
}
catch (Exception ex)
{
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs
index a849289feb..958cd43d7b 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs
@@ -73,7 +73,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
if (string.IsNullOrWhiteSpace(value) == false)
{
- return value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
+ return value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
}
return null;
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs
index c8fdc06a42..afa4f48249 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs
@@ -39,7 +39,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
yield break;
}
- var fileNames = selectedFiles?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ var fileNames = selectedFiles?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
if (fileNames == null || !fileNames.Any())
yield break;
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs
index 20f44ae433..6b3b7e68cb 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs
@@ -35,6 +35,12 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
+ private static readonly JsonSerializerSettings ImageCropperValueJsonSerializerSettings = new JsonSerializerSettings
+ {
+ Culture = CultureInfo.InvariantCulture,
+ FloatParseHandling = FloatParseHandling.Decimal
+ };
+
///
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
{
@@ -44,11 +50,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
ImageCropperValue value;
try
{
- value = JsonConvert.DeserializeObject(sourceString, new JsonSerializerSettings
- {
- Culture = CultureInfo.InvariantCulture,
- FloatParseHandling = FloatParseHandling.Decimal
- });
+ value = JsonConvert.DeserializeObject(sourceString, ImageCropperValueJsonSerializerSettings);
}
catch (Exception ex)
{
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
index 4c05f56d5c..ef1d808b9a 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
@@ -4,13 +4,16 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
+using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Events;
+using Umbraco.Cms.Core.Exceptions;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Runtime;
using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Infrastructure.Migrations.Install;
+using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
using Umbraco.Cms.Infrastructure.Persistence;
-using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Infrastructure.Runtime
{
@@ -25,6 +28,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime
private readonly IUmbracoDatabaseFactory _databaseFactory;
private readonly IEventAggregator _eventAggregator;
private readonly IHostingEnvironment _hostingEnvironment;
+ private readonly DatabaseBuilder _databaseBuilder;
+ private readonly IUmbracoVersion _umbracoVersion;
///
/// Initializes a new instance of the class.
@@ -38,7 +43,9 @@ namespace Umbraco.Cms.Infrastructure.Runtime
IMainDom mainDom,
IUmbracoDatabaseFactory databaseFactory,
IEventAggregator eventAggregator,
- IHostingEnvironment hostingEnvironment)
+ IHostingEnvironment hostingEnvironment,
+ DatabaseBuilder databaseBuilder,
+ IUmbracoVersion umbracoVersion)
{
State = state;
_loggerFactory = loggerFactory;
@@ -49,6 +56,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime
_databaseFactory = databaseFactory;
_eventAggregator = eventAggregator;
_hostingEnvironment = hostingEnvironment;
+ _databaseBuilder = databaseBuilder;
+ _umbracoVersion = umbracoVersion;
_logger = _loggerFactory.CreateLogger();
}
@@ -86,7 +95,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
if (State.Level <= RuntimeLevel.BootFailed)
{
- throw new InvalidOperationException($"Cannot start the runtime if the runtime level is less than or equal to {RuntimeLevel.BootFailed}");
+ return; // The exception will be rethrown by BootFailedMiddelware
}
IApplicationShutdownRegistry hostingEnvironmentLifetime = _applicationShutdownRegistry;
@@ -98,12 +107,35 @@ namespace Umbraco.Cms.Infrastructure.Runtime
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
AcquireMainDom();
+ // if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade
+ if (State.Reason == RuntimeLevelReason.UpgradeMigrations && State.Level == RuntimeLevel.Run)
+ {
+ // do the upgrade
+ DoUnattendedUpgrade();
+
+ // upgrade is done, set reason to Run
+ DetermineRuntimeLevel();
+
+ }
+
await _eventAggregator.PublishAsync(new UmbracoApplicationStarting(State.Level), cancellationToken);
// create & initialize the components
_components.Initialize();
}
+ private void DoUnattendedUpgrade()
+ {
+ var plan = new UmbracoPlan(_umbracoVersion);
+ using (_profilingLogger.TraceDuration("Starting unattended upgrade.", "Unattended upgrade completed."))
+ {
+ var result = _databaseBuilder.UpgradeSchemaAndData(plan);
+ if (result.Success == false)
+ throw new UnattendedInstallException("An error occurred while running the unattended upgrade.\n" + result.Message);
+ }
+
+ }
+
private void DoUnattendedInstall()
{
State.DoUnattendedInstall();
@@ -148,11 +180,12 @@ namespace Umbraco.Cms.Infrastructure.Runtime
_databaseFactory.ConfigureForUpgrade();
}
}
- catch
+ catch (Exception ex)
{
State.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException);
timer?.Fail();
- throw;
+ _logger.LogError(ex, "Boot Failed");
+ // We do not throw the exception. It will be rethrown by BootFailedMiddleware
}
}
}
diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
index 63828715fd..737f3550f6 100644
--- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
+++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
@@ -7,6 +7,7 @@ using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using NPoco;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
@@ -28,25 +29,28 @@ namespace Umbraco.Cms.Infrastructure.Runtime
private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom";
private const string UpdatedSuffix = "_updated";
private readonly ILogger _logger;
+ private readonly IOptions _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private IUmbracoDatabase _db;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
- private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider();
+ private SqlServerSyntaxProvider _sqlServerSyntax;
private bool _mainDomChanging = false;
private readonly UmbracoDatabaseFactory _dbFactory;
private bool _errorDuringAcquiring;
private object _locker = new object();
private bool _hasTable = false;
- public SqlMainDomLock(ILogger logger, ILoggerFactory loggerFactory, GlobalSettings globalSettings, ConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator, IHostingEnvironment hostingEnvironment, DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
+ public SqlMainDomLock(ILogger logger, ILoggerFactory loggerFactory, IOptions globalSettings, IOptions connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator, IHostingEnvironment hostingEnvironment, DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
{
// unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer
_lockId = Guid.NewGuid().ToString();
_logger = logger;
-_hostingEnvironment = hostingEnvironment;
+ _globalSettings = globalSettings;
+ _sqlServerSyntax = new SqlServerSyntaxProvider(_globalSettings);
+ _hostingEnvironment = hostingEnvironment;
_dbFactory = new UmbracoDatabaseFactory(loggerFactory.CreateLogger(),
loggerFactory,
- globalSettings,
+ _globalSettings,
connectionStrings,
new Lazy(() => new MapperCollection(Enumerable.Empty())),
dbProviderFactoryCreator,
diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs
index fc8f5f3912..02d4375186 100644
--- a/src/Umbraco.Infrastructure/RuntimeState.cs
+++ b/src/Umbraco.Infrastructure/RuntimeState.cs
@@ -30,6 +30,7 @@ namespace Umbraco.Cms.Core
///
/// The initial
+ /// The initial
///
public static RuntimeState Booting() => new RuntimeState() { Level = RuntimeLevel.Boot };
@@ -115,7 +116,8 @@ namespace Umbraco.Cms.Core
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase;
- throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
+ BootFailedException =new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
+ throw BootFailedException;
}
case UmbracoDatabaseState.NotInstalled:
{
@@ -132,7 +134,7 @@ namespace Umbraco.Cms.Core
// although the files version matches the code version, the database version does not
// which means the local files have been upgraded but not the database - need to upgrade
_logger.LogDebug("Has not reached the final upgrade step, need to upgrade Umbraco.");
- Level = RuntimeLevel.Upgrade;
+ Level = _unattendedSettings.Value.UpgradeUnattended ? RuntimeLevel.Run : RuntimeLevel.Upgrade;
Reason = RuntimeLevelReason.UpgradeMigrations;
}
break;
@@ -191,7 +193,8 @@ namespace Umbraco.Cms.Core
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState;
- throw new BootFailedException("Could not check the upgrade state.", e);
+ BootFailedException = new BootFailedException("Could not check the upgrade state.", e);
+ throw BootFailedException;
}
}
@@ -250,9 +253,12 @@ namespace Umbraco.Cms.Core
_logger.LogInformation(ex, "Error during unattended install.");
database.AbortTransaction();
- throw new UnattendedInstallException(
+ var innerException = new UnattendedInstallException(
"The database configuration failed with the following message: " + ex.Message
+ "\n Please check log file for additional information (can be found in '/App_Data/Logs/')");
+ BootFailedException = new BootFailedException(innerException.Message, innerException);
+
+ throw BootFailedException;
}
}
}
diff --git a/src/Umbraco.Infrastructure/Scoping/IScope.cs b/src/Umbraco.Infrastructure/Scoping/IScope.cs
index 7a6a62a6c7..4f55988d2f 100644
--- a/src/Umbraco.Infrastructure/Scoping/IScope.cs
+++ b/src/Umbraco.Infrastructure/Scoping/IScope.cs
@@ -58,5 +58,19 @@ namespace Umbraco.Cms.Core.Scoping
///
/// The lock object identifiers.
void WriteLock(params int[] lockIds);
+
+ ///
+ /// Write-locks some lock objects.
+ ///
+ /// The database timeout in milliseconds
+ /// The lock object identifier.
+ void WriteLock(TimeSpan timeout, int lockId);
+
+ ///
+ /// Read-locks some lock objects.
+ ///
+ /// The database timeout in milliseconds
+ /// The lock object identifier.
+ void ReadLock(TimeSpan timeout, int lockId);
}
}
diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs
index 603a83c197..66a4470645 100644
--- a/src/Umbraco.Infrastructure/Scoping/Scope.cs
+++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs
@@ -621,7 +621,13 @@ namespace Umbraco.Cms.Core.Scoping
///
public void ReadLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.ReadLock(Database, lockIds);
+ ///
+ public void ReadLock(TimeSpan timeout, int lockId) => Database.SqlContext.SqlSyntax.ReadLock(Database, timeout, lockId);
+
///
public void WriteLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.WriteLock(Database, lockIds);
+
+ ///
+ public void WriteLock(TimeSpan timeout, int lockId) => Database.SqlContext.SqlSyntax.WriteLock(Database, timeout, lockId);
}
}
diff --git a/src/Umbraco.Infrastructure/Search/ExamineComponent.cs b/src/Umbraco.Infrastructure/Search/ExamineComponent.cs
index 4535bebd8b..30dc01dc9a 100644
--- a/src/Umbraco.Infrastructure/Search/ExamineComponent.cs
+++ b/src/Umbraco.Infrastructure/Search/ExamineComponent.cs
@@ -276,10 +276,20 @@ namespace Umbraco.Cms.Infrastructure.Search
break;
case MessageType.RefreshByPayload:
var payload = (MemberCacheRefresher.JsonPayload[])args.MessageObject;
- var members = payload.Select(x => _services.MemberService.GetById(x.Id));
- foreach (var m in members)
+ foreach (var p in payload)
{
- ReIndexForMember(m);
+ if (p.Removed)
+ {
+ DeleteIndexForEntity(p.Id, false);
+ }
+ else
+ {
+ var m = _services.MemberService.GetById(p.Id);
+ if (m != null)
+ {
+ ReIndexForMember(m);
+ }
+ }
}
break;
case MessageType.RefreshAll:
@@ -693,6 +703,7 @@ namespace Umbraco.Cms.Infrastructure.Search
List valueSet = builders[index.PublishedValuesOnly].Value;
index.IndexItems(valueSet);
}
+
return Task.CompletedTask;
});
}
diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs
index 505052b514..2bb9b1ab8d 100644
--- a/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs
+++ b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs
@@ -36,12 +36,6 @@ namespace Umbraco.Cms.Core.Security
ClaimsIdentity baseIdentity = await base.GenerateClaimsAsync(user);
- // now we can flow any custom claims that the actual user has currently assigned which could be done in the OnExternalLogin callback
- foreach (IdentityUserClaim claim in user.Claims)
- {
- baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue));
- }
-
baseIdentity.AddRequiredClaims(
user.Id,
user.UserName,
@@ -53,6 +47,10 @@ namespace Umbraco.Cms.Core.Security
user.AllowedSections,
user.Roles.Select(x => x.RoleId).ToArray());
+ // now we can flow any custom claims that the actual user has currently
+ // assigned which could be done in the OnExternalLogin callback
+ baseIdentity.MergeClaimsFromBackOfficeIdentity(user);
+
return new ClaimsPrincipal(baseIdentity);
}
diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
index 41f7f94113..bd05ce0461 100644
--- a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
+++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
@@ -33,6 +34,7 @@ namespace Umbraco.Cms.Core.Security
private readonly IExternalLoginService _externalLoginService;
private readonly GlobalSettings _globalSettings;
private readonly UmbracoMapper _mapper;
+ private readonly AppCaches _appCaches;
///
/// Initializes a new instance of the class.
@@ -44,7 +46,8 @@ namespace Umbraco.Cms.Core.Security
IExternalLoginService externalLoginService,
IOptions globalSettings,
UmbracoMapper mapper,
- IdentityErrorDescriber describer)
+ IdentityErrorDescriber describer,
+ AppCaches appCaches)
: base(describer)
{
_scopeProvider = scopeProvider;
@@ -53,6 +56,7 @@ namespace Umbraco.Cms.Core.Security
_externalLoginService = externalLoginService ?? throw new ArgumentNullException(nameof(externalLoginService));
_globalSettings = globalSettings.Value;
_mapper = mapper;
+ _appCaches = appCaches;
_userService = userService;
_externalLoginService = externalLoginService;
}
@@ -685,8 +689,8 @@ namespace Umbraco.Cms.Core.Security
}
// we should re-set the calculated start nodes
- identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService);
- identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService);
+ identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches);
+ identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches);
// reset all changes
identityUser.ResetDirtyProperties(false);
diff --git a/src/Umbraco.Infrastructure/Security/ClaimsIdentityExtensions.cs b/src/Umbraco.Infrastructure/Security/ClaimsIdentityExtensions.cs
new file mode 100644
index 0000000000..1a37376070
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Security/ClaimsIdentityExtensions.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+using System.Linq;
+using System.Security.Claims;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Security;
+
+namespace Umbraco.Extensions
+{
+ public static class MergeClaimsIdentityExtensions
+ {
+ // Ignore these Claims when merging, these claims are dynamically added whenever the ticket
+ // is re-issued and we don't want to merge old values of these.
+ private static readonly string[] s_ignoredClaims = new[] { ClaimTypes.CookiePath, Constants.Security.SessionIdClaimType };
+
+ public static void MergeClaimsFromBackOfficeIdentity(this ClaimsIdentity destination, ClaimsIdentity source)
+ {
+ foreach (Claim claim in source.Claims
+ .Where(claim => !s_ignoredClaims.Contains(claim.Type))
+ .Where(claim => !destination.HasClaim(claim.Type, claim.Value)))
+ {
+ destination.AddClaim(new Claim(claim.Type, claim.Value));
+ }
+ }
+
+ public static void MergeClaimsFromBackOfficeIdentity(this ClaimsIdentity destination, BackOfficeIdentityUser source)
+ {
+ foreach (Microsoft.AspNetCore.Identity.IdentityUserClaim claim in source.Claims
+ .Where(claim => !s_ignoredClaims.Contains(claim.ClaimType))
+ .Where(claim => !destination.HasClaim(claim.ClaimType, claim.ClaimValue)))
+ {
+ destination.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue));
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
index 50c4d1e505..0cf724ec20 100644
--- a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
+++ b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
@@ -3,7 +3,7 @@
using System;
using Microsoft.Extensions.Options;
-using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
@@ -17,13 +17,19 @@ namespace Umbraco.Cms.Core.Security
{
private readonly ILocalizedTextService _textService;
private readonly IEntityService _entityService;
- private readonly GlobalSettings _globalSettings;
+ private readonly IOptions _globalSettings;
+ private readonly AppCaches _appCaches;
- public IdentityMapDefinition(ILocalizedTextService textService, IEntityService entityService, IOptions globalSettings)
+ public IdentityMapDefinition(
+ ILocalizedTextService textService,
+ IEntityService entityService,
+ IOptions globalSettings,
+ AppCaches appCaches)
{
_textService = textService;
_entityService = entityService;
- _globalSettings = globalSettings.Value;
+ _globalSettings = globalSettings;
+ _appCaches = appCaches;
}
public void DefineMaps(UmbracoMapper mapper)
@@ -31,7 +37,7 @@ namespace Umbraco.Cms.Core.Security
mapper.Define(
(source, context) =>
{
- var target = new BackOfficeIdentityUser(_globalSettings, source.Id, source.Groups);
+ var target = new BackOfficeIdentityUser(_globalSettings.Value, source.Id, source.Groups);
target.DisableChangeTracking();
return target;
},
@@ -67,8 +73,8 @@ namespace Umbraco.Cms.Core.Security
target.Groups = source.Groups.ToArray();
*/
- target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService);
- target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService);
+ target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches);
+ target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService, _appCaches);
target.Email = source.Email;
target.UserName = source.Username;
target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime();
@@ -80,7 +86,7 @@ namespace Umbraco.Cms.Core.Security
target.PasswordConfig = source.PasswordConfiguration;
target.StartContentIds = source.StartContentIds;
target.StartMediaIds = source.StartMediaIds;
- target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); // project CultureInfo to string
+ target.Culture = source.GetUserCulture(_textService, _globalSettings.Value).ToString(); // project CultureInfo to string
target.IsApproved = source.IsApproved;
target.SecurityStamp = source.SecurityStamp;
target.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null;
diff --git a/src/Umbraco.Infrastructure/Serialization/NoTypeConverterJsonConverter.cs b/src/Umbraco.Infrastructure/Serialization/NoTypeConverterJsonConverter.cs
index ebdc84b39a..0b5c9acc49 100644
--- a/src/Umbraco.Infrastructure/Serialization/NoTypeConverterJsonConverter.cs
+++ b/src/Umbraco.Infrastructure/Serialization/NoTypeConverterJsonConverter.cs
@@ -19,6 +19,7 @@ namespace Umbraco.Cms.Infrastructure.Serialization
public class NoTypeConverterJsonConverter : JsonConverter
{
static readonly IContractResolver resolver = new NoTypeConverterContractResolver();
+ private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings { ContractResolver = resolver };
private class NoTypeConverterContractResolver : DefaultContractResolver
{
@@ -41,12 +42,12 @@ namespace Umbraco.Cms.Infrastructure.Serialization
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
- return JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Deserialize(reader, objectType);
+ return JsonSerializer.CreateDefault(JsonSerializerSettings).Deserialize(reader, objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
- JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Serialize(writer, value);
+ JsonSerializer.CreateDefault(JsonSerializerSettings).Serialize(writer, value);
}
}
}
diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs
index 017540ae3f..b6e91c717d 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs
@@ -512,7 +512,7 @@ namespace Umbraco.Cms.Core.Services.Implement
if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty();
var rootId = Cms.Core.Constants.System.RootString;
- var ids = content.Path.Split(',')
+ var ids = content.Path.Split(Constants.CharArrays.Comma)
.Where(x => x != rootId && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray();
if (ids.Any() == false)
return new List();
diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index b23887fb18..d5c051d0a7 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -877,7 +877,7 @@ namespace Umbraco.Cms.Core.Services.Implement
public IEnumerable GetContainers(TItem item)
{
- var ancestorIds = item.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ var ancestorIds = item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.Select(x =>
{
var asInt = x.TryConvertTo();
diff --git a/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs b/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs
index dacaa7e228..640fa50cc6 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs
@@ -115,7 +115,7 @@ namespace Umbraco.Cms.Core.Services.Implement
public IEnumerable GetContainers(IDataType dataType)
{
- var ancestorIds = dataType.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ var ancestorIds = dataType.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.Select(x =>
{
var asInt = x.TryConvertTo();
diff --git a/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs
index bc8b4f25ce..b63040a4a0 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs
@@ -564,6 +564,13 @@ namespace Umbraco.Cms.Core.Services.Implement
new XAttribute("path", contentBase.Path),
new XAttribute("isDoc", ""));
+
+ // Add culture specific node names
+ foreach (var culture in contentBase.AvailableCultures)
+ {
+ xml.Add(new XAttribute("nodeName-" + culture, contentBase.GetCultureName(culture)));
+ }
+
foreach (var property in contentBase.Properties)
xml.Add(SerializeProperty(property, published));
diff --git a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs
index 60061ed9bf..4b9d6f8e5c 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs
@@ -482,7 +482,7 @@ namespace Umbraco.Cms.Core.Services.Implement
if (media.Path.IsNullOrWhiteSpace()) return Enumerable.Empty();
var rootId = Cms.Core.Constants.System.RootString;
- var ids = media.Path.Split(',')
+ var ids = media.Path.Split(Constants.CharArrays.Comma)
.Where(x => x != rootId && x != media.Id.ToString(CultureInfo.InvariantCulture))
.Select(int.Parse)
.ToArray();
diff --git a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs
index e91aa8ce33..aedad0e56b 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs
@@ -82,7 +82,7 @@ namespace Umbraco.Cms.Core.Services.Implement
if (entitiesL.Count == 0) return;
//put all entity's paths into a list with the same indices
- var paths = entitiesL.Select(x => x.Path.Split(',').Select(int.Parse).ToArray()).ToArray();
+ var paths = entitiesL.Select(x => x.Path.Split(Constants.CharArrays.Comma).Select(int.Parse).ToArray()).ToArray();
// lazily get versions
var prevVersionDictionary = new Dictionary();
@@ -180,7 +180,7 @@ namespace Umbraco.Cms.Core.Services.Implement
///
public IEnumerable FilterUserNotificationsByPath(IEnumerable userNotifications, string path)
{
- var pathParts = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ var pathParts = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
return userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList();
}
diff --git a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs
index 4c8615f442..19df11e798 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs
@@ -55,7 +55,7 @@ namespace Umbraco.Cms.Core.Services.Implement
{
//Get all ids in the path for the content item and ensure they all
// parse to ints that are not -1.
- var ids = contentPath.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ var ids = contentPath.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.Select(x => int.TryParse(x, out int val) ? val : -1)
.Where(x => x != -1)
.ToList();
@@ -63,12 +63,10 @@ namespace Umbraco.Cms.Core.Services.Implement
//start with the deepest id
ids.Reverse();
- using (var scope = ScopeProvider.CreateScope())
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
//This will retrieve from cache!
- var entries = _publicAccessRepository.GetMany().ToArray();
-
- scope.Complete();
+ var entries = _publicAccessRepository.GetMany().ToList();
foreach (var id in ids)
{
var found = entries.FirstOrDefault(x => x.ProtectedNodeId == id);
diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
index 23c1af2c5d..99642069f3 100644
--- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
+++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
@@ -3,6 +3,9 @@
netstandard2.0
Umbraco.Cms.Infrastructure
+ Umbraco.Cms.Infrastructure
+ Umbraco CMS Infrastructure
+ Contains the infrastructure assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
diff --git a/src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs b/src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs
index c46b2ef6f2..60e456a651 100644
--- a/src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs
+++ b/src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs
@@ -2,7 +2,9 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
+using Microsoft.Extensions.Options;
using NPoco;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
@@ -17,6 +19,13 @@ namespace Umbraco.Cms.Persistence.SqlCe
///
public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase
{
+ private readonly IOptions _globalSettings;
+
+ public SqlCeSyntaxProvider(IOptions globalSettings)
+ {
+ _globalSettings = globalSettings;
+ }
+
public override string ProviderName => Constants.DatabaseProviders.SqlCe;
public override Sql SelectTop(Sql sql, int top)
@@ -89,7 +98,7 @@ namespace Umbraco.Cms.Persistence.SqlCe
string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns)
? GetQuotedColumnName(columnDefinition.Name)
: string.Join(", ", columnDefinition.PrimaryKeyColumns
- .Split(new[]{',', ' '}, StringSplitOptions.RemoveEmptyEntries)
+ .Split(Constants.CharArrays.CommaSpace, StringSplitOptions.RemoveEmptyEntries)
.Select(GetQuotedColumnName));
return string.Format(CreateConstraint,
@@ -162,6 +171,16 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
return result > 0;
}
+ public override void WriteLock(IDatabase db, TimeSpan timeout, int lockId)
+ {
+ // soon as we get Database, a transaction is started
+
+ if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
+ throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
+
+ ObtainWriteLock(db, timeout, lockId);
+ }
+
public override void WriteLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
@@ -169,16 +188,32 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
- db.Execute(@"SET LOCK_TIMEOUT 1800;");
- // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
+ var timeout = _globalSettings.Value.SqlWriteLockTimeOut;
+
foreach (var lockId in lockIds)
{
- var i = db.Execute(@"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
- if (i == 0) // ensure we are actually locking!
- throw new ArgumentException($"LockObject with id={lockId} does not exist.");
+ ObtainWriteLock(db, timeout, lockId);
}
}
+ private static void ObtainWriteLock(IDatabase db, TimeSpan timeout, int lockId)
+ {
+ db.Execute(@"SET LOCK_TIMEOUT " + timeout.TotalMilliseconds + ";");
+ var i = db.Execute(@"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
+ if (i == 0) // ensure we are actually locking!
+ throw new ArgumentException($"LockObject with id={lockId} does not exist.");
+ }
+
+ public override void ReadLock(IDatabase db, TimeSpan timeout, int lockId)
+ {
+ // soon as we get Database, a transaction is started
+
+ if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
+ throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
+
+ ObtainReadLock(db, timeout, lockId);
+ }
+
public override void ReadLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
@@ -186,15 +221,25 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
- // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
- var i = db.ExecuteScalar("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
- if (i == null) // ensure we are actually locking!
- throw new ArgumentException($"LockObject with id={lockId} does not exist.");
+ ObtainReadLock(db, null, lockId);
}
}
+ private static void ObtainReadLock(IDatabase db, TimeSpan? timeout, int lockId)
+ {
+ if (timeout.HasValue)
+ {
+ db.Execute(@"SET LOCK_TIMEOUT " + timeout.Value.TotalMilliseconds + ";");
+ }
+
+ var i = db.ExecuteScalar("SELECT value FROM umbracoLock WHERE id=@id", new {id = lockId});
+
+ if (i == null) // ensure we are actually locking!
+ throw new ArgumentException($"LockObject with id={lockId} does not exist.");
+ }
+
protected override string FormatIdentity(ColumnDefinition column)
{
return column.IsIdentity ? GetIdentityString(column) : string.Empty;
diff --git a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs
index e4c43b1067..5428279655 100644
--- a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs
+++ b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs
@@ -83,7 +83,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
var pos = route.IndexOf('/');
var path = pos == 0 ? route : route.Substring(pos);
var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos));
- var parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+ var parts = path.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries);
IPublishedContent content;
diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs
index 00346233ba..bfde5c07bc 100644
--- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs
+++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs
@@ -719,17 +719,18 @@ AND cmsContentNu.nodeId IS NULL
return s;
}
+ private static readonly JsonSerializerSettings NestedContentDataJsonSerializerSettings = new JsonSerializerSettings
+ {
+ Converters = new List { new ForceInt32Converter() }
+ };
+
private static ContentNestedData DeserializeNestedData(string data)
{
// by default JsonConvert will deserialize our numeric values as Int64
// which is bad, because they were Int32 in the database - take care
- var settings = new JsonSerializerSettings
- {
- Converters = new List { new ForceInt32Converter() }
- };
-
- return JsonConvert.DeserializeObject(data, settings);
+ return JsonConvert.DeserializeObject(data, NestedContentDataJsonSerializerSettings
+ );
}
}
}
diff --git a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj
index fe3ce47de4..9aea12912c 100644
--- a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj
+++ b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj
@@ -3,6 +3,10 @@
netstandard2.0
Umbraco.Cms.Infrastructure.PublishedCache
+ 8
+ Umbraco.Cms.PublishedCache.NuCache
+ Umbraco CMS Published Cache
+ Contains the Published Cache assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts
index ecfe3e95d8..e12ba5ef75 100644
--- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts
+++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts
@@ -570,7 +570,7 @@ context('Content', () => {
// Create content with content picker
cy.get('.umb-tree-root-link').rightclick();
- cy.get('.-opens-dialog > .umb-action-link').click();
+ cy.get('[data-element="action-create"]').click();
cy.get('[data-element="action-create-' + pickerDocTypeAlias + '"] > .umb-action-link').click();
// Fill out content
cy.umbracoEditorHeaderName('ContentPickerContent');
diff --git a/src/Umbraco.Tests.Benchmarks/JsonSerializerSettingsBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/JsonSerializerSettingsBenchmarks.cs
new file mode 100644
index 0000000000..7f419547bd
--- /dev/null
+++ b/src/Umbraco.Tests.Benchmarks/JsonSerializerSettingsBenchmarks.cs
@@ -0,0 +1,69 @@
+using BenchmarkDotNet.Attributes;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Umbraco.Tests.Benchmarks.Config;
+
+namespace Umbraco.Tests.Benchmarks
+{
+ [QuickRunConfig]
+ [MemoryDiagnoser]
+ public class JsonSerializerSettingsBenchmarks
+ {
+ [Benchmark]
+ public void SerializerSettingsInstantiation()
+ {
+ int instances = 1000;
+ for (int i = 0; i < instances; i++)
+ {
+ new JsonSerializerSettings();
+ }
+ }
+
+ [Benchmark(Baseline =true)]
+ public void SerializerSettingsSingleInstantiation()
+ {
+ new JsonSerializerSettings();
+ }
+
+// // * Summary *
+
+// BenchmarkDotNet=v0.11.3, OS=Windows 10.0.18362
+//Intel Core i5-8265U CPU 1.60GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
+// [Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.8.4250.0
+// Job-JIATTD : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.8.4250.0
+
+//IterationCount=3 IterationTime=100.0000 ms LaunchCount = 1
+//WarmupCount=3
+
+// Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
+//-------------------------------------- |-------------:|-------------:|------------:|-------:|--------:|------------:|------------:|------------:|--------------------:|
+// SerializerSettingsInstantiation | 29,120.48 ns | 5,532.424 ns | 303.2508 ns | 997.84 | 23.66 | 73.8122 | - | - | 232346 B |
+// SerializerSettingsSingleInstantiation | 29.19 ns | 8.089 ns | 0.4434 ns | 1.00 | 0.00 | 0.0738 | - | - | 232 B |
+
+//// * Warnings *
+//MinIterationTime
+// JsonSerializerSettingsBenchmarks.SerializerSettingsSingleInstantiation: IterationCount= 3, IterationTime= 100.0000 ms, LaunchCount= 1, WarmupCount= 3->MinIterationTime = 96.2493 ms which is very small. It's recommended to increase it.
+
+//// * Legends *
+// Mean : Arithmetic mean of all measurements
+// Error : Half of 99.9% confidence interval
+// StdDev : Standard deviation of all measurements
+// Ratio : Mean of the ratio distribution ([Current]/[Baseline])
+// RatioSD : Standard deviation of the ratio distribution([Current]/[Baseline])
+// Gen 0/1k Op : GC Generation 0 collects per 1k Operations
+// Gen 1/1k Op : GC Generation 1 collects per 1k Operations
+// Gen 2/1k Op : GC Generation 2 collects per 1k Operations
+// Allocated Memory/Op : Allocated memory per single operation(managed only, inclusive, 1KB = 1024B)
+// 1 ns : 1 Nanosecond(0.000000001 sec)
+
+//// * Diagnostic Output - MemoryDiagnoser *
+
+
+// // ***** BenchmarkRunner: End *****
+// Run time: 00:00:04 (4.88 sec), executed benchmarks: 2
+ }
+}
diff --git a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs
index 3a0cc8f66e..f54ab96255 100644
--- a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs
+++ b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs
@@ -1,7 +1,9 @@
using System;
using System.Linq.Expressions;
using BenchmarkDotNet.Attributes;
+using Microsoft.Extensions.Options;
using Moq;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
@@ -17,7 +19,7 @@ namespace Umbraco.Tests.Benchmarks
protected Lazy MockSqlContext()
{
var sqlContext = Mock.Of();
- var syntax = new SqlCeSyntaxProvider();
+ var syntax = new SqlCeSyntaxProvider(Options.Create(new GlobalSettings()));
Mock.Get(sqlContext).Setup(x => x.SqlSyntax).Returns(syntax);
return new Lazy(() => sqlContext);
}
@@ -34,7 +36,7 @@ namespace Umbraco.Tests.Benchmarks
_mapperCollection = mapperCollection.Object;
}
- private readonly ISqlSyntaxProvider _syntaxProvider = new SqlCeSyntaxProvider();
+ private readonly ISqlSyntaxProvider _syntaxProvider = new SqlCeSyntaxProvider(Options.Create(new GlobalSettings()));
private readonly CachedExpression _cachedExpression;
private readonly IMapperCollection _mapperCollection;
diff --git a/src/Umbraco.Tests.Benchmarks/SqlTemplatesBenchmark.cs b/src/Umbraco.Tests.Benchmarks/SqlTemplatesBenchmark.cs
index 286307aa17..89ada16387 100644
--- a/src/Umbraco.Tests.Benchmarks/SqlTemplatesBenchmark.cs
+++ b/src/Umbraco.Tests.Benchmarks/SqlTemplatesBenchmark.cs
@@ -1,7 +1,9 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
+using Microsoft.Extensions.Options;
using NPoco;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Persistence.SqlCe;
@@ -34,7 +36,7 @@ namespace Umbraco.Tests.Benchmarks
var mappers = new NPoco.MapperCollection { new PocoMapper() };
var factory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init());
- SqlContext = new SqlContext(new SqlCeSyntaxProvider(), DatabaseType.SQLCe, factory);
+ SqlContext = new SqlContext(new SqlCeSyntaxProvider(Options.Create(new GlobalSettings())), DatabaseType.SQLCe, factory);
SqlTemplates = new SqlTemplates(SqlContext);
}
diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj
index fce12ded71..d1d88fc870 100644
--- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj
+++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj
@@ -53,6 +53,7 @@
+
diff --git a/src/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs b/src/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs
index ecc11784b4..188c515bf0 100644
--- a/src/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs
+++ b/src/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs
@@ -8,10 +8,12 @@ using System.Data;
using System.Data.Common;
using System.Linq.Expressions;
using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
using Moq;
using NPoco;
using NPoco.DatabaseTypes;
using NPoco.Linq;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Infrastructure.Migrations.Install;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
@@ -37,7 +39,7 @@ namespace Umbraco.Cms.Tests.Common.TestHelpers
public TestDatabase(DatabaseType databaseType = null, ISqlSyntaxProvider syntaxProvider = null)
{
DatabaseType = databaseType ?? new SqlServerDatabaseType();
- SqlContext = new SqlContext(syntaxProvider ?? new SqlServerSyntaxProvider(), DatabaseType, Mock.Of());
+ SqlContext = new SqlContext(syntaxProvider ?? new SqlServerSyntaxProvider(Options.Create((new GlobalSettings()))), DatabaseType, Mock.Of());
}
///
diff --git a/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj
index 1040df225d..a13d6b763b 100644
--- a/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj
+++ b/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj
@@ -3,6 +3,9 @@
netstandard2.0
Umbraco.Cms.Tests.Common
+ Umbraco.Cms.Tests
+ Umbraco CMS Test Tools
+ Contains commonly used tools to write tests for Umbraco CMS, such as various builders for content etc.
diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
index 484eea9b95..8e897011d2 100644
--- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
+++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
@@ -121,7 +121,7 @@ namespace Umbraco.Cms.Tests.Integration.Implementations
public IWebHostEnvironment GetWebHostEnvironment() => _hostEnvironment;
public override IDbProviderFactoryCreator DbProviderFactoryCreator =>
- new SqlServerDbProviderFactoryCreator(DbProviderFactories.GetFactory);
+ new SqlServerDbProviderFactoryCreator(DbProviderFactories.GetFactory, Options.Create(new GlobalSettings()));
public override IBulkSqlInsertProvider BulkSqlInsertProvider => new SqlServerBulkSqlInsertProvider();
diff --git a/src/Umbraco.Tests.Integration/Testing/TestUmbracoDatabaseFactoryProvider.cs b/src/Umbraco.Tests.Integration/Testing/TestUmbracoDatabaseFactoryProvider.cs
index cba2c51a30..b53e55a323 100644
--- a/src/Umbraco.Tests.Integration/Testing/TestUmbracoDatabaseFactoryProvider.cs
+++ b/src/Umbraco.Tests.Integration/Testing/TestUmbracoDatabaseFactoryProvider.cs
@@ -46,8 +46,8 @@ namespace Umbraco.Cms.Tests.Integration.Testing
return new UmbracoDatabaseFactory(
_loggerFactory.CreateLogger(),
_loggerFactory,
- _globalSettings.Value,
- _connectionStrings.Value,
+ _globalSettings,
+ _connectionStrings,
_mappers,
_dbProviderFactoryCreator,
_databaseSchemaCreatorFactory);
diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs
index e42e681a58..b68bb267cd 100644
--- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs
+++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs
@@ -1,16 +1,16 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
using NPoco;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
-using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence
{
@@ -322,6 +322,147 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence
Assert.IsNull(e2);
}
+ [Test]
+ public void Throws_When_Lock_Timeout_Is_Exceeded()
+ {
+ using (ExecutionContext.SuppressFlow())
+ {
+
+
+ var t1 = Task.Run(() =>
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+
+ Console.WriteLine("Write lock A");
+ // This will acquire right away
+ scope.WriteLock(TimeSpan.FromMilliseconds(2000), Constants.Locks.ContentTree);
+ Thread.Sleep(6000); // Wait longer than the Read Lock B timeout
+ scope.Complete();
+ Console.WriteLine("Finished Write lock A");
+ }
+ });
+
+ Thread.Sleep(500); // 100% sure task 1 starts first
+
+ var t2 = Task.Run(() =>
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ Console.WriteLine("Read lock B");
+
+ // This will wait for the write lock to release but it isn't going to wait long
+ // enough so an exception will be thrown.
+ Assert.Throws(() =>
+ scope.ReadLock(TimeSpan.FromMilliseconds(3000), Constants.Locks.ContentTree));
+ scope.Complete();
+ Console.WriteLine("Finished Read lock B");
+ }
+ });
+
+ var t3 = Task.Run(() =>
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ Console.WriteLine("Write lock C");
+
+ // This will wait for the write lock to release but it isn't going to wait long
+ // enough so an exception will be thrown.
+ Assert.Throws(() =>
+ scope.WriteLock(TimeSpan.FromMilliseconds(3000), Constants.Locks.ContentTree));
+
+ scope.Complete();
+ Console.WriteLine("Finished Write lock C");
+ }
+ });
+
+ Task.WaitAll(t1, t2, t3);
+ }
+ }
+
+ [Test]
+ public void Read_Lock_Waits_For_Write_Lock()
+ {
+ var locksCompleted = 0;
+
+ using (ExecutionContext.SuppressFlow())
+ {
+ var t1 = Task.Run(() =>
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ Console.WriteLine("Write lock A");
+ // This will acquire right away
+ scope.WriteLock(TimeSpan.FromMilliseconds(2000), Constants.Locks.ContentTree);
+ Thread.Sleep(4000); // Wait less than the Read Lock B timeout
+ scope.Complete();
+ Interlocked.Increment(ref locksCompleted);
+ Console.WriteLine("Finished Write lock A");
+ }
+ });
+
+ Thread.Sleep(500); // 100% sure task 1 starts first
+
+ var t2 = Task.Run(() =>
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ Console.WriteLine("Read lock B");
+
+ // This will wait for the write lock to release
+ Assert.DoesNotThrow(() =>
+ scope.ReadLock(TimeSpan.FromMilliseconds(6000), Constants.Locks.ContentTree));
+
+ Assert.GreaterOrEqual(locksCompleted, 1);
+
+ scope.Complete();
+ Interlocked.Increment(ref locksCompleted);
+ Console.WriteLine("Finished Read lock B");
+ }
+ });
+
+
+ var t3 = Task.Run(() =>
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ Console.WriteLine("Read lock C");
+
+ // This will wait for the write lock to release
+ Assert.DoesNotThrow(() =>
+ scope.ReadLock(TimeSpan.FromMilliseconds(6000), Constants.Locks.ContentTree));
+
+ Assert.GreaterOrEqual(locksCompleted, 1);
+
+ scope.Complete();
+ Interlocked.Increment(ref locksCompleted);
+ Console.WriteLine("Finished Read lock C");
+ }
+ });
+
+ Task.WaitAll(t1, t2, t3);
+ }
+
+ Assert.AreEqual(3, locksCompleted);
+ }
+
+ [Test]
+ public void Lock_Exceeds_Command_Timeout()
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ var realDb = (Database)scope.Database;
+ realDb.CommandTimeout = 1000;
+
+ Console.WriteLine("Write lock A");
+ // TODO: In theory this would throw
+ scope.WriteLock(TimeSpan.FromMilliseconds(3000), Constants.Locks.ContentTree);
+ scope.Complete();
+ Console.WriteLine("Finished Write lock A");
+ }
+ }
+
+
private void NoDeadLockTestThread(int id, EventWaitHandle myEv, WaitHandle otherEv, ref Exception exception)
{
using (var scope = ScopeProvider.CreateScope())
diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs
index d109afd944..366e8b067d 100644
--- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs
+++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs
@@ -747,7 +747,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos
[Test]
public void AliasRegexTest()
{
- System.Text.RegularExpressions.Regex regex = new SqlServerSyntaxProvider().AliasRegex;
+ System.Text.RegularExpressions.Regex regex = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings())).AliasRegex;
Assert.AreEqual(@"(\[\w+]\.\[\w+])\s+AS\s+(\[\w+])", regex.ToString());
const string sql = "SELECT [table].[column1] AS [alias1], [table].[column2] AS [alias2] FROM [table];";
System.Text.RegularExpressions.MatchCollection matches = regex.Matches(sql);
diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs
index a44d7ca252..55a348aad7 100644
--- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs
+++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs
@@ -220,6 +220,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence
}
}
+ [Test]
+ public void Can_Create_umbracoLogViewerQuery_Table()
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion);
+
+ helper.CreateTable();
+
+ scope.Complete();
+ }
+ }
+
[Test]
public void Can_Create_umbracoLanguage_Table()
{
diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs
index 848792a2d1..800b702888 100644
--- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs
+++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs
@@ -1,9 +1,11 @@
using System;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Moq;
using NPoco;
using NUnit.Framework;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Infrastructure.Migrations;
using Umbraco.Cms.Infrastructure.Migrations.Expressions.Common.Expressions;
using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create.Index;
@@ -78,7 +80,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine,
[Test]
public void Format_SqlServer_NonClusteredIndexDefinition_AddsNonClusteredDirective()
{
- var sqlSyntax = new SqlServerSyntaxProvider();
+ var sqlSyntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
var indexDefinition = CreateIndexDefinition();
indexDefinition.IndexType = IndexTypes.NonClustered;
@@ -90,7 +92,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine,
[Test]
public void Format_SqlServer_NonClusteredIndexDefinition_UsingIsClusteredFalse_AddsClusteredDirective()
{
- var sqlSyntax = new SqlServerSyntaxProvider();
+ var sqlSyntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
var indexDefinition = CreateIndexDefinition();
indexDefinition.IndexType = IndexTypes.Clustered;
@@ -103,7 +105,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine,
public void CreateIndexBuilder_SqlServer_NonClustered_CreatesNonClusteredIndex()
{
var logger = Mock.Of>();
- var sqlSyntax = new SqlServerSyntaxProvider();
+ var sqlSyntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax);
var context = new MigrationContext(db, logger);
@@ -124,7 +126,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine,
public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex()
{
var logger = Mock.Of>();
- var sqlSyntax = new SqlServerSyntaxProvider();
+ var sqlSyntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax);
var context = new MigrationContext(db, logger);
@@ -145,7 +147,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine,
public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex_Multi_Columnn()
{
var logger = Mock.Of>();
- var sqlSyntax = new SqlServerSyntaxProvider();
+ var sqlSyntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax);
var context = new MigrationContext(db, logger);
@@ -166,7 +168,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine,
public void CreateIndexBuilder_SqlServer_Clustered_CreatesClusteredIndex()
{
var logger = Mock.Of>();
- var sqlSyntax = new SqlServerSyntaxProvider();
+ var sqlSyntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax);
var context = new MigrationContext(db, logger);
diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/Assets/logviewer.searches.config.js b/src/Umbraco.Tests.UnitTests/TestHelpers/Assets/logviewer.searches.config.js
deleted file mode 100644
index 25ee9b2242..0000000000
--- a/src/Umbraco.Tests.UnitTests/TestHelpers/Assets/logviewer.searches.config.js
+++ /dev/null
@@ -1,42 +0,0 @@
-[
- {
- "name": "Find all logs where the Level is NOT Verbose and NOT Debug",
- "query": "Not(@Level='Verbose') and Not(@Level='Debug')"
- },
- {
- "name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)",
- "query": "Has(@Exception)"
- },
- {
- "name": "Find all logs that have the property 'Duration'",
- "query": "Has(Duration)"
- },
- {
- "name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
- "query": "Has(Duration) and Duration > 1000"
- },
- {
- "name": "Find all logs that are from the namespace 'Umbraco.Core'",
- "query": "StartsWith(SourceContext, 'Umbraco.Core')"
- },
- {
- "name": "Find all logs that use a specific log message template",
- "query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'"
- },
- {
- "name": "Find logs where one of the items in the SortedComponentTypes property array is equal to",
- "query": "SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'"
- },
- {
- "name": "Find logs where one of the items in the SortedComponentTypes property array contains",
- "query": "Contains(SortedComponentTypes[?], 'DatabaseServer')"
- },
- {
- "name": "Find all logs that the message has localhost in it with SQL like",
- "query": "@Message like '%localhost%'"
- },
- {
- "name": "Find all logs that the message that starts with 'end' in it with SQL like",
- "query": "@Message like 'end%'"
- }
-]
diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs
index e1eb437282..dce144803f 100644
--- a/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs
+++ b/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs
@@ -4,10 +4,12 @@
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using Moq;
using NPoco;
using NUnit.Framework;
using Umbraco.Cms.Core.Composing;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
@@ -41,7 +43,7 @@ namespace Umbraco.Cms.Tests.UnitTests.TestHelpers
IServiceProvider factory = composition.CreateServiceProvider();
var pocoMappers = new NPoco.MapperCollection { new PocoMapper() };
var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init());
- var sqlSyntax = new SqlServerSyntaxProvider();
+ var sqlSyntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
SqlContext = new SqlContext(sqlSyntax, DatabaseType.SqlServer2012, pocoDataFactory, new Lazy(() => factory.GetRequiredService()));
Mappers = factory.GetRequiredService();
}
diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
index 81276ba562..7b0f190566 100644
--- a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
+++ b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
@@ -94,7 +94,7 @@ namespace Umbraco.Cms.Tests.UnitTests.TestHelpers
public static Lazy GetMockSqlContext()
{
ISqlContext sqlContext = Mock.Of();
- var syntax = new SqlServerSyntaxProvider();
+ var syntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
Mock.Get(sqlContext).Setup(x => x.SqlSyntax).Returns(syntax);
return new Lazy(() => sqlContext);
}
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs
index 556fb42ff0..ae51d77a93 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
+using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Services;
@@ -44,7 +45,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models
.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny()))
.Returns((type, ids) => new[] { new TreeEntityPath { Id = startNodeId, Path = startNodePath } });
- Assert.AreEqual(outcome, user.HasPathAccess(content, esmock.Object));
+ Assert.AreEqual(outcome, user.HasPathAccess(content, esmock.Object, AppCaches.Disabled));
}
[TestCase("", "1", "1")] // single user start, top level
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserGroupTests.cs
new file mode 100644
index 0000000000..eccd20c182
--- /dev/null
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserGroupTests.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using Newtonsoft.Json;
+using NUnit.Framework;
+using Umbraco.Cms.Core.Models.Membership;
+using Umbraco.Cms.Tests.Common.Builders;
+using Umbraco.Cms.Tests.Common.Builders.Extensions;
+
+namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models
+{
+ [TestFixture]
+ public class UserGroupTests
+ {
+ private UserGroupBuilder _builder;
+
+ [SetUp]
+ public void SetUp() => _builder = new UserGroupBuilder();
+
+ [Test]
+ public void Can_Deep_Clone()
+ {
+ IUserGroup item = Build();
+
+ var clone = (IUserGroup)item.DeepClone();
+
+ var x = clone.Equals(item);
+ Assert.AreNotSame(clone, item);
+ Assert.AreEqual(clone, item);
+
+ Assert.AreEqual(clone.AllowedSections.Count(), item.AllowedSections.Count());
+ Assert.AreNotSame(clone.AllowedSections, item.AllowedSections);
+
+ // Verify normal properties with reflection
+ PropertyInfo[] allProps = clone.GetType().GetProperties();
+ foreach (PropertyInfo propertyInfo in allProps)
+ {
+ Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null));
+ }
+ }
+
+ [Test]
+ public void Can_Serialize_Without_Error()
+ {
+ IUserGroup item = Build();
+
+ var json = JsonConvert.SerializeObject(item);
+ Debug.Print(json);
+ }
+
+ private IUserGroup Build() =>
+ _builder
+ .WithId(3)
+ .WithAllowedSections(new List(){"A", "B"})
+ .Build();
+
+
+ }
+
+
+}
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/ContentPermissionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/ContentPermissionsTests.cs
index fc52aaf275..9bbd353516 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/ContentPermissionsTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/ContentPermissionsTests.cs
@@ -3,6 +3,7 @@
using Moq;
using NUnit.Framework;
+using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
@@ -31,7 +32,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IEntityService entityService = entityServiceMock.Object;
var userServiceMock = new Mock();
IUserService userService = userServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent);
@@ -58,7 +59,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IUserService userService = userServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' });
@@ -87,7 +88,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny()))
.Returns(new[] { Mock.Of(entity => entity.Id == 9876 && entity.Path == "-1,9876") });
IEntityService entityService = entityServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' });
@@ -117,7 +118,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IUserService userService = userServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' });
@@ -147,7 +148,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IUserService userService = userServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' });
@@ -167,7 +168,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IUserService userService = userServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-1, user, out IContent _);
@@ -187,7 +188,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IUserService userService = userServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-20, user, out IContent _);
@@ -209,7 +210,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny()))
.Returns(new[] { Mock.Of(entity => entity.Id == 1234 && entity.Path == "-1,1234") });
IEntityService entityService = entityServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-20, user, out IContent foundContent);
@@ -232,7 +233,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny()))
.Returns(new[] { Mock.Of(entity => entity.Id == 1234 && entity.Path == "-1,1234") });
IEntityService entityService = entityServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-1, user, out IContent foundContent);
@@ -259,7 +260,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IUserService userService = userServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-1, user, out IContent foundContent, new[] { 'A' });
@@ -286,7 +287,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IEntityService entityService = entityServiceMock.Object;
var contentServiceMock = new Mock();
IContentService contentService = contentServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-1, user, out IContent foundContent, new[] { 'B' });
@@ -314,7 +315,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IEntityService entityService = entityServiceMock.Object;
var contentServiceMock = new Mock();
IContentService contentService = contentServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-20, user, out IContent foundContent, new[] { 'A' });
@@ -341,7 +342,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IEntityService entityService = entityServiceMock.Object;
var contentServiceMock = new Mock();
IContentService contentService = contentServiceMock.Object;
- var contentPermissions = new ContentPermissions(userService, contentService, entityService);
+ var contentPermissions = new ContentPermissions(userService, contentService, entityService, AppCaches.Disabled);
// Act
ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-20, user, out IContent foundContent, new[] { 'B' });
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/MediaPermissionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/MediaPermissionsTests.cs
index 370e968a76..b719b57c37 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/MediaPermissionsTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/MediaPermissionsTests.cs
@@ -3,6 +3,7 @@
using Moq;
using NUnit.Framework;
+using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
@@ -29,7 +30,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IMediaService mediaService = mediaServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var mediaPermissions = new MediaPermissions(mediaService, entityService);
+ var mediaPermissions = new MediaPermissions(mediaService, entityService, AppCaches.Disabled);
// Act
MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, 1234, out _);
@@ -51,7 +52,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IMediaService mediaService = mediaServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var mediaPermissions = new MediaPermissions(mediaService, entityService);
+ var mediaPermissions = new MediaPermissions(mediaService, entityService, AppCaches.Disabled);
// Act/assert
MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, 1234, out _);
@@ -73,7 +74,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny()))
.Returns(new[] { Mock.Of(entity => entity.Id == 9876 && entity.Path == "-1,9876") });
IEntityService entityService = entityServiceMock.Object;
- var mediaPermissions = new MediaPermissions(mediaService, entityService);
+ var mediaPermissions = new MediaPermissions(mediaService, entityService, AppCaches.Disabled);
// Act
MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, 1234, out _);
@@ -91,7 +92,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IMediaService mediaService = mediaServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var mediaPermissions = new MediaPermissions(mediaService, entityService);
+ var mediaPermissions = new MediaPermissions(mediaService, entityService, AppCaches.Disabled);
// Act
MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, -1, out _);
@@ -111,7 +112,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny()))
.Returns(new[] { Mock.Of(entity => entity.Id == 1234 && entity.Path == "-1,1234") });
IEntityService entityService = entityServiceMock.Object;
- var mediaPermissions = new MediaPermissions(mediaService, entityService);
+ var mediaPermissions = new MediaPermissions(mediaService, entityService, AppCaches.Disabled);
// Act
MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, -1, out _);
@@ -129,7 +130,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
IMediaService mediaService = mediaServiceMock.Object;
var entityServiceMock = new Mock();
IEntityService entityService = entityServiceMock.Object;
- var mediaPermissions = new MediaPermissions(mediaService, entityService);
+ var mediaPermissions = new MediaPermissions(mediaService, entityService, AppCaches.Disabled);
// Act
MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, -21, out _);
@@ -149,7 +150,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security
entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny()))
.Returns(new[] { Mock.Of(entity => entity.Id == 1234 && entity.Path == "-1,1234") });
IEntityService entityService = entityServiceMock.Object;
- var mediaPermissions = new MediaPermissions(mediaService, entityService);
+ var mediaPermissions = new MediaPermissions(mediaService, entityService, AppCaches.Disabled);
// Act
MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, -21, out _);
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs
index 0cc7346d0e..94064ddaba 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs
@@ -6,6 +6,7 @@ using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Editors;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
@@ -34,7 +35,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]);
@@ -55,7 +57,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]);
@@ -76,7 +79,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] { "FunGroup" });
@@ -97,13 +101,44 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] { "test" });
Assert.IsTrue(result.Success);
}
+ [Test]
+ [TestCase(Constants.Security.AdminGroupAlias, Constants.Security.AdminGroupAlias, ExpectedResult = true)]
+ [TestCase(Constants.Security.AdminGroupAlias, "SomethingElse", ExpectedResult = true)]
+ [TestCase(Constants.Security.EditorGroupAlias, Constants.Security.AdminGroupAlias, ExpectedResult = false)]
+ [TestCase(Constants.Security.EditorGroupAlias, "SomethingElse", ExpectedResult = false)]
+ [TestCase(Constants.Security.EditorGroupAlias, Constants.Security.EditorGroupAlias, ExpectedResult = true)]
+ public bool Can_only_add_user_groups_you_are_part_of_yourself_unless_you_are_admin(string groupAlias, string groupToAdd)
+ {
+ var currentUser = Mock.Of(user => user.Groups == new[]
+ {
+ new ReadOnlyUserGroup(1, "CurrentUser", "icon-user", null, null, groupAlias, new string[0], new string[0])
+ });
+ IUser savingUser = null; // This means it is a new created user
+
+ var contentService = new Mock();
+ var mediaService = new Mock();
+ var userService = new Mock();
+ var entityService = new Mock();
+
+ var authHelper = new UserEditorAuthorizationHelper(
+ contentService.Object,
+ mediaService.Object,
+ entityService.Object,
+ AppCaches.Disabled);
+
+ var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] { groupToAdd });
+
+ return result.Success;
+ }
+
[Test]
public void Can_Add_Another_Content_Start_Node_On_User_With_Access()
{
@@ -130,7 +165,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
// adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234, 5555 }, new int[0], new string[0]);
@@ -164,7 +200,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
// removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234 }, new int[0], new string[0]);
@@ -198,7 +235,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
// adding 1234 but currentUser doesn't have access to it ... nope
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234 }, new int[0], new string[0]);
@@ -232,7 +270,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
// adding 5555 which currentUser has access to since it's a child of 9876 ... ok
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 5555 }, new int[0], new string[0]);
@@ -266,7 +305,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
// adding 1234 but currentUser doesn't have access to it ... nope
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234 }, new string[0]);
@@ -300,7 +340,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
// adding 5555 which currentUser has access to since it's a child of 9876 ... ok
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 5555 }, new string[0]);
@@ -334,7 +375,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
// adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234, 5555 }, new string[0]);
@@ -368,7 +410,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Editors
var authHelper = new UserEditorAuthorizationHelper(
contentService.Object,
mediaService.Object,
- entityService.Object);
+ entityService.Object,
+ AppCaches.Disabled);
// removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok
Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234 }, new string[0]);
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs
index 87002824f7..a68b6f9200 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs
@@ -9,6 +9,7 @@ using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Examine;
@@ -20,7 +21,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Examine
[Test]
public void Invalid_Category()
{
- var validator = new ContentValueSetValidator(false, true, Mock.Of());
+ var validator = new ContentValueSetValidator(
+ false,
+ true,
+ Mock.Of(),
+ Mock.Of());
ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" }));
Assert.AreEqual(ValueSetValidationResult.Valid, result);
@@ -35,7 +40,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Examine
[Test]
public void Must_Have_Path()
{
- var validator = new ContentValueSetValidator(false, true, Mock.Of());
+ var validator = new ContentValueSetValidator(
+ false,
+ true,
+ Mock.Of(),
+ Mock.Of());
ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world" }));
Assert.AreEqual(ValueSetValidationResult.Failed, result);
@@ -47,7 +56,12 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Examine
[Test]
public void Parent_Id()
{
- var validator = new ContentValueSetValidator(false, true, Mock.Of(), 555);
+ var validator = new ContentValueSetValidator(
+ false,
+ true,
+ Mock.Of(),
+ Mock.Of(),
+ 555);
ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" }));
Assert.AreEqual(ValueSetValidationResult.Filtered, result);
@@ -123,6 +137,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Examine
false,
true,
Mock.Of(),
+ Mock.Of(),
includeItemTypes: new List { "include-content" });
ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" }));
@@ -142,6 +157,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Examine
false,
true,
Mock.Of(),
+ Mock.Of(),
excludeItemTypes: new List { "exclude-content" });
ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" }));
@@ -161,6 +177,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Examine
false,
true,
Mock.Of(),
+ Mock.Of(),
includeItemTypes: new List { "include-content", "exclude-content" },
excludeItemTypes: new List { "exclude-content" });
@@ -180,7 +197,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Examine
[Test]
public void Recycle_Bin_Content()
{
- var validator = new ContentValueSetValidator(true, false, Mock.Of());
+ var validator = new ContentValueSetValidator(
+ true,
+ false,
+ Mock.Of(),
+ Mock.Of());
ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,-20,555" }));
Assert.AreEqual(ValueSetValidationResult.Failed, result);
@@ -206,7 +227,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Examine
[Test]
public void Recycle_Bin_Media()
{
- var validator = new ContentValueSetValidator(true, false, Mock.Of