diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index e97b03e7e3..1acc6b602b 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -57,6 +57,7 @@ Or alternatively, you can reach out directly to any of the team members behind t
* 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)
+* Emma Burstow (She, Her - Languages spoken: English) [ema@umbraco.com](mailto:ema@umbraco.com)
The review process is done with full respect for the privacy and security of the reporter of any incident.
@@ -89,4 +90,4 @@ Consequence: A permanent ban from any sort of public interaction within the comm
## 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
+This Code of Conduct will be maintained and reviewed by the team listed above.
diff --git a/.globalconfig b/.globalconfig
index 8342ab4580..8c0929382d 100644
--- a/.globalconfig
+++ b/.globalconfig
@@ -49,6 +49,7 @@ dotnet_analyzer_diagnostic.category-StyleCop.CSharp.MaintainabilityRules.severit
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.LayoutRules.severity = suggestion
dotnet_diagnostic.SA1636.severity = none # SA1636: File header copyright text should match
+dotnet_diagnostic.SA1101.severity = none # PrefixLocalCallsWithThis - stylecop appears to be ignoring dotnet_style_qualification_for_*
dotnet_diagnostic.SA1503.severity = warning # BracesMustNotBeOmitted
dotnet_diagnostic.SA1117.severity = warning # ParametersMustBeOnSameLineOrSeparateLines
diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index a699d05148..ca6e7ad05e 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -538,7 +538,7 @@ stages:
inputs:
targetType: inline
script: |
- choco install docfx --version=2.58.5 -y
+ choco install docfx --version=2.59.0 -y
if ($lastexitcode -ne 0){
throw ("Error installing DocFX")
}
diff --git a/build/templates/UmbracoProject/.gitignore b/build/templates/UmbracoProject/.gitignore
index 199d371b48..f4caa41045 100644
--- a/build/templates/UmbracoProject/.gitignore
+++ b/build/templates/UmbracoProject/.gitignore
@@ -461,6 +461,9 @@ $RECYCLE.BIN/
# Dont commit Umbraco TEMP folder containing Examine Indexes, NuCache etc
**/umbraco/Data/TEMP/
+# Umbraco log files
+**/umbraco/Logs/
+
# Dont commit files that are generated and cached from the default ImageSharp location
**/umbraco/mediacache/
@@ -484,4 +487,4 @@ $RECYCLE.BIN/
# Umbraco Static Assets of Backoffice
# Nuget package Umbraco.Cms.StaticAssets will copy them in during dotnet build
-**/wwwroot/umbraco/
\ No newline at end of file
+**/wwwroot/umbraco/
diff --git a/src/ApiDocs/umbracotemplate/partials/class.tmpl.partial b/src/ApiDocs/umbracotemplate/partials/class.tmpl.partial
index 9153a863a4..aa50d597ba 100644
--- a/src/ApiDocs/umbracotemplate/partials/class.tmpl.partial
+++ b/src/ApiDocs/umbracotemplate/partials/class.tmpl.partial
@@ -15,8 +15,8 @@
{{item.name.0.value}}
{{/inheritance.0}}
-{{__global.namespace}} :{{namespace}}
-{{__global.assembly}} :{{assemblies.0}}.dll
+{{__global.namespace}} : {{{namespace.specName.0.value}}}
+{{__global.assembly}} : {{assemblies.0}}.dll
{{__global.syntax}}
{{syntax.content.0.value}}
diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs
index 73c5ea18f5..f9aa6b500c 100644
--- a/src/JsonSchema/AppSettings.cs
+++ b/src/JsonSchema/AppSettings.cs
@@ -3,6 +3,7 @@
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Deploy.Core.Configuration.DebugConfiguration;
using Umbraco.Deploy.Core.Configuration.DeployConfiguration;
using Umbraco.Deploy.Core.Configuration.DeployProjectConfiguration;
using Umbraco.Forms.Core.Configuration;
@@ -127,6 +128,8 @@ namespace JsonSchema
public DeploySettings Settings { get; set; }
public DeployProjectConfig Project { get; set; }
+
+ public DebugSettings Debug { get; set; }
}
}
}
diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj
index f0652e7e98..e46fc3ee4b 100644
--- a/src/JsonSchema/JsonSchema.csproj
+++ b/src/JsonSchema/JsonSchema.csproj
@@ -13,6 +13,8 @@
+
+
diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
index 1caa81d80a..e6e5c7006f 100644
--- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
@@ -156,6 +156,9 @@ namespace Umbraco.Cms.Core.Configuration.Models
internal const bool StaticShowDeprecatedPropertyEditors = false;
internal const string StaticLoginBackgroundImage = "assets/img/login.jpg";
internal const string StaticLoginLogoImage = "assets/img/application/umbraco_logo_white.svg";
+ internal const bool StaticHideBackOfficeLogo = false;
+ internal const bool StaticDisableDeleteWhenReferenced = false;
+ internal const bool StaticDisableUnpublishWhenReferenced = false;
///
/// Gets or sets a value for the content notification settings.
@@ -219,6 +222,24 @@ namespace Umbraco.Cms.Core.Configuration.Models
[DefaultValue(StaticLoginLogoImage)]
public string LoginLogoImage { get; set; } = StaticLoginLogoImage;
+ ///
+ /// Gets or sets a value indicating whether to hide the backoffice umbraco logo or not.
+ ///
+ [DefaultValue(StaticHideBackOfficeLogo)]
+ public bool HideBackOfficeLogo { get; set; } = StaticHideBackOfficeLogo;
+
+ ///
+ /// Gets or sets a value indicating whether to disable the deletion of items referenced by other items.
+ ///
+ [DefaultValue(StaticDisableDeleteWhenReferenced)]
+ public bool DisableDeleteWhenReferenced { get; set; } = StaticDisableDeleteWhenReferenced;
+
+ ///
+ /// Gets or sets a value indicating whether to disable the unpublishing of items referenced by other items.
+ ///
+ [DefaultValue(StaticDisableUnpublishWhenReferenced)]
+ public bool DisableUnpublishWhenReferenced { get; set; } = StaticDisableUnpublishWhenReferenced;
+
///
/// Get or sets the model representing the global content version cleanup policy
///
diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
index 31068efd9f..5e42d3b8be 100644
--- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
@@ -195,18 +195,18 @@ namespace Umbraco.Cms.Core.Configuration.Models
public bool IsPickupDirectoryLocationConfigured => !string.IsNullOrWhiteSpace(Smtp?.PickupDirectoryLocation);
///
- /// Gets a value indicating whether TinyMCE scripting sanitization should be applied.
+ /// Gets or sets a value indicating whether TinyMCE scripting sanitization should be applied.
///
[DefaultValue(StaticSanitizeTinyMce)]
- public bool SanitizeTinyMce => StaticSanitizeTinyMce;
+ public bool SanitizeTinyMce { get; set; } = StaticSanitizeTinyMce;
///
- /// Gets a value representing the time in milliseconds to lock the database for a write operation.
+ /// An int value representing the time in milliseconds to lock the database for a write operation
///
///
/// The default value is 5000 milliseconds.
///
[DefaultValue(StaticSqlWriteLockTimeOut)]
- public TimeSpan SqlWriteLockTimeOut { get; } = TimeSpan.Parse(StaticSqlWriteLockTimeOut);
+ public TimeSpan SqlWriteLockTimeOut { get; set; } = TimeSpan.Parse(StaticSqlWriteLockTimeOut);
}
}
diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs
index 2cf1f770b7..73d046de32 100644
--- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs
@@ -49,6 +49,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
if (!ModelsMode.IsAuto())
{
_flagOutOfDateModels = false;
+ return;
}
_flagOutOfDateModels = value;
diff --git a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs
index 6ea563c741..de8215a51b 100644
--- a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs
@@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
[UmbracoOptions(Constants.Configuration.ConfigRichTextEditor)]
public class RichTextEditorSettings
{
- internal const string StaticValidElements = "+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[*]";
+ internal const string StaticValidElements = "+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[*],figure,figcaption";
internal const string StaticInvalidElements = "font";
private static readonly string[] s_default_plugins = new[]
diff --git a/src/Umbraco.Core/Constants-HttpClients.cs b/src/Umbraco.Core/Constants-HttpClients.cs
new file mode 100644
index 0000000000..474ec49a50
--- /dev/null
+++ b/src/Umbraco.Core/Constants-HttpClients.cs
@@ -0,0 +1,19 @@
+namespace Umbraco.Cms.Core
+{
+ ///
+ /// Defines constants.
+ ///
+ public static partial class Constants
+ {
+ ///
+ /// Defines constants for named http clients.
+ ///
+ public static class HttpClients
+ {
+ ///
+ /// Name for http client which ignores certificate errors.
+ ///
+ public const string IgnoreCertificateErrors = "Umbraco:HttpClients:IgnoreCertificateErrors";
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs
index 62e19008dd..39980f116a 100644
--- a/src/Umbraco.Core/Constants-Icons.cs
+++ b/src/Umbraco.Core/Constants-Icons.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Cms.Core
+namespace Umbraco.Cms.Core
{
public static partial class Constants
{
@@ -9,6 +9,11 @@
///
public const string DefaultIcon = Content;
+ ///
+ /// System blueprint icon
+ ///
+ public const string Blueprint = "icon-blueprint";
+
///
/// System content icon
///
diff --git a/src/Umbraco.Core/Constants-SystemDirectories.cs b/src/Umbraco.Core/Constants-SystemDirectories.cs
index bf34aab989..f70dd199fc 100644
--- a/src/Umbraco.Core/Constants-SystemDirectories.cs
+++ b/src/Umbraco.Core/Constants-SystemDirectories.cs
@@ -1,3 +1,5 @@
+using System;
+
namespace Umbraco.Cms.Core
{
public static partial class Constants
@@ -42,9 +44,11 @@ namespace Umbraco.Cms.Core
public const string Install = "~/install";
public const string AppPlugins = "/App_Plugins";
- public static string AppPluginIcons => "/Backoffice/Icons";
- public const string CreatedPackages = "/created-packages";
+ [Obsolete("Use PluginIcons instead")]
+ public static string AppPluginIcons => "/Backoffice/Icons";
+
+ public const string PluginIcons = "/backoffice/icons";
public const string MvcViews = "~/Views";
@@ -54,6 +58,8 @@ namespace Umbraco.Cms.Core
public const string Packages = Data + "/packages";
+ public const string CreatedPackages = Data + "/CreatedPackages";
+
public const string Preview = Data + "/preview";
///
diff --git a/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs b/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs
new file mode 100644
index 0000000000..b1fb31d2aa
--- /dev/null
+++ b/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models.ContentEditing;
+using Umbraco.Cms.Core.Models.Membership;
+
+namespace Umbraco.Cms.Core.ContentApps
+{
+ internal class DictionaryContentAppFactory : IContentAppFactory
+ {
+ private const int Weight = -100;
+
+ private ContentApp _dictionaryApp;
+
+ public ContentApp GetContentAppFor(object source, IEnumerable userGroups)
+ {
+ switch (source)
+ {
+ case IDictionaryItem _:
+ return _dictionaryApp ??= new ContentApp
+ {
+ Alias = "dictionaryContent",
+ Name = "Content",
+ Icon = "icon-document",
+ View = "views/dictionary/views/content/content.html",
+ Weight = Weight
+ };
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
index 811ee35c14..a0ff6104a7 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
@@ -46,7 +46,8 @@ namespace Umbraco.Cms.Core.DependencyInjection
.Append()
.Append()
.Append()
- .Append();
+ .Append()
+ .Append();
// all built-in finders in the correct order,
// devs can then modify this list on application startup
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
index d6fb9da396..36dc181580 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
@@ -265,6 +265,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton();
// Register telemetry service used to gather data about installed packages
+ Services.AddUnique();
Services.AddUnique();
Services.AddUnique();
diff --git a/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs b/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs
index 0a6518a1ca..3a73173127 100644
--- a/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs
+++ b/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs
@@ -33,7 +33,8 @@ namespace Umbraco.Cms.Core.Events
Constants.Conventions.RelationTypes.RelateDocumentOnCopyName,
true,
Constants.ObjectTypes.Document,
- Constants.ObjectTypes.Document);
+ Constants.ObjectTypes.Document,
+ false);
_relationService.Save(relationType);
}
diff --git a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs
index bceddf1fd6..0319be3297 100644
--- a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs
+++ b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs
@@ -132,7 +132,7 @@ namespace Umbraco.Extensions
}
}
- verifiedIdentity = identity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType ? identity : new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType);
+ verifiedIdentity = identity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType ? identity : new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType);
return true;
}
diff --git a/src/Umbraco.Core/Extensions/ObjectExtensions.cs b/src/Umbraco.Core/Extensions/ObjectExtensions.cs
index d666046e4b..3ae7f65ba5 100644
--- a/src/Umbraco.Core/Extensions/ObjectExtensions.cs
+++ b/src/Umbraco.Core/Extensions/ObjectExtensions.cs
@@ -626,7 +626,6 @@ namespace Umbraco.Extensions
if (type == typeof(sbyte)) return XmlConvert.ToString((sbyte)value);
if (type == typeof(short)) return XmlConvert.ToString((short)value);
if (type == typeof(TimeSpan)) return XmlConvert.ToString((TimeSpan)value);
- if (type == typeof(bool)) return XmlConvert.ToString((bool)value);
if (type == typeof(uint)) return XmlConvert.ToString((uint)value);
if (type == typeof(ulong)) return XmlConvert.ToString((ulong)value);
if (type == typeof(ushort)) return XmlConvert.ToString((ushort)value);
diff --git a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs
index 37769afc53..d95fa6919d 100644
--- a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs
+++ b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs
@@ -1,10 +1,13 @@
using System;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Install.Models;
+using Umbraco.Cms.Core.Telemetry;
+using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Core.Install.InstallSteps
{
@@ -13,31 +16,29 @@ namespace Umbraco.Cms.Core.Install.InstallSteps
PerformsAppRestart = false)]
public class TelemetryIdentifierStep : InstallSetupStep
{
- private readonly ILogger _logger;
private readonly IOptions _globalSettings;
- private readonly IConfigManipulator _configManipulator;
+ private readonly ISiteIdentifierService _siteIdentifierService;
- public TelemetryIdentifierStep(ILogger logger, IOptions globalSettings, IConfigManipulator configManipulator)
+ public TelemetryIdentifierStep(
+ IOptions globalSettings,
+ ISiteIdentifierService siteIdentifierService)
{
- _logger = logger;
_globalSettings = globalSettings;
- _configManipulator = configManipulator;
+ _siteIdentifierService = siteIdentifierService;
+ }
+
+ [Obsolete("Use constructor that takes GlobalSettings and ISiteIdentifierService")]
+ public TelemetryIdentifierStep(
+ ILogger logger,
+ IOptions globalSettings,
+ IConfigManipulator configManipulator)
+ : this(globalSettings, StaticServiceProvider.Instance.GetRequiredService())
+ {
}
public override Task ExecuteAsync(object model)
{
- // Generate GUID
- var telemetrySiteIdentifier = Guid.NewGuid();
-
- try
- {
- _configManipulator.SetGlobalId(telemetrySiteIdentifier.ToString());
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Couldn't update config files with a telemetry site identifier");
- }
-
+ _siteIdentifierService.TryCreateSiteIdentifier(out _);
return Task.FromResult(null);
}
diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs
index d865bb01aa..0bb555f16f 100644
--- a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs
+++ b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@@ -70,6 +70,10 @@ namespace Umbraco.Cms.Core.Manifest
partA = "contentType";
partB = contentType.Alias;
break;
+ case IDictionaryItem _:
+ partA = "dictionary";
+ partB = "*"; //Not really a different type for dictionary items
+ break;
default:
return null;
diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs
index 3648f7907e..844e4d1283 100644
--- a/src/Umbraco.Core/Models/Content.cs
+++ b/src/Umbraco.Core/Models/Content.cs
@@ -198,7 +198,7 @@ namespace Umbraco.Cms.Core.Models
public bool IsCulturePublished(string culture)
// just check _publishInfos
// a non-available culture could not become published anyways
- => _publishInfos != null && _publishInfos.ContainsKey(culture);
+ => !culture.IsNullOrWhiteSpace() && _publishInfos != null && _publishInfos.ContainsKey(culture);
///
public bool IsCultureEdited(string culture)
diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs
index 41e49ba34d..d8cfaf1104 100644
--- a/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs
+++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
@@ -17,6 +17,7 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
{
Notifications = new List();
Translations = new List();
+ ContentApps = new List();
}
///
@@ -37,5 +38,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
///
[DataMember(Name = "translations")]
public List Translations { get; private set; }
+
+ ///
+ /// Apps for the dictionary item
+ ///
+ [DataMember(Name = "apps")]
+ public List ContentApps { get; private set; }
}
}
diff --git a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs
index b7bfb32808..a0d9bbbcb3 100644
--- a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs
+++ b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs
@@ -1,17 +1,34 @@
using System.Runtime.Serialization;
+using Umbraco.Cms.Core.Models.Entities;
namespace Umbraco.Cms.Core.Models.ContentEditing
{
[DataContract(Name = "historyCleanup", Namespace = "")]
- public class HistoryCleanup
+ public class HistoryCleanup : BeingDirtyBase
{
+ private bool _preventCleanup;
+ private int? _keepAllVersionsNewerThanDays;
+ private int? _keepLatestVersionPerDayForDays;
+
[DataMember(Name = "preventCleanup")]
- public bool PreventCleanup { get; set; }
+ public bool PreventCleanup
+ {
+ get => _preventCleanup;
+ set => SetPropertyValueAndDetectChanges(value, ref _preventCleanup, nameof(PreventCleanup));
+ }
[DataMember(Name = "keepAllVersionsNewerThanDays")]
- public int? KeepAllVersionsNewerThanDays { get; set; }
+ public int? KeepAllVersionsNewerThanDays
+ {
+ get => _keepAllVersionsNewerThanDays;
+ set => SetPropertyValueAndDetectChanges(value, ref _keepAllVersionsNewerThanDays, nameof(KeepAllVersionsNewerThanDays));
+ }
[DataMember(Name = "keepLatestVersionPerDayForDays")]
- public int? KeepLatestVersionPerDayForDays { get; set; }
+ public int? KeepLatestVersionPerDayForDays
+ {
+ get => _keepLatestVersionPerDayForDays;
+ set => SetPropertyValueAndDetectChanges(value, ref _keepLatestVersionPerDayForDays, nameof(KeepLatestVersionPerDayForDays));
+ }
}
}
diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs
index 27f0f525df..6a4c8e5f81 100644
--- a/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs
+++ b/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs
@@ -55,5 +55,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
///
[DataMember(Name = "notifications")]
public List Notifications { get; private set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries.
+ ///
+ [DataMember(Name = "isDependency", IsRequired = true)]
+ public bool IsDependency { get; set; }
}
}
diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs
index b72a03eec4..f541158095 100644
--- a/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs
+++ b/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs
@@ -23,5 +23,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
///
[DataMember(Name = "childObjectType", IsRequired = false)]
public Guid? ChildObjectType { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries.
+ ///
+ [DataMember(Name = "isDependency", IsRequired = true)]
+ public bool IsDependency { get; set; }
}
}
diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs
index 6ff94f57f3..a252aa4723 100644
--- a/src/Umbraco.Core/Models/ContentType.cs
+++ b/src/Umbraco.Core/Models/ContentType.cs
@@ -96,7 +96,13 @@ namespace Umbraco.Cms.Core.Models
}
}
- public HistoryCleanup HistoryCleanup { get; set; }
+ private HistoryCleanup _historyCleanup;
+
+ public HistoryCleanup HistoryCleanup
+ {
+ get => _historyCleanup;
+ set => SetPropertyValueAndDetectChanges(value, ref _historyCleanup, nameof(HistoryCleanup));
+ }
///
/// Determines if AllowedTemplates contains templateId
@@ -162,5 +168,8 @@ namespace Umbraco.Cms.Core.Models
///
IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) =>
(IContentType)DeepCloneWithResetIdentities(newAlias);
+
+ ///
+ public override bool IsDirty() => base.IsDirty() || HistoryCleanup.IsDirty();
}
}
diff --git a/src/Umbraco.Core/Models/IRelationType.cs b/src/Umbraco.Core/Models/IRelationType.cs
index 9efde4b939..3ee1517f55 100644
--- a/src/Umbraco.Core/Models/IRelationType.cs
+++ b/src/Umbraco.Core/Models/IRelationType.cs
@@ -4,6 +4,15 @@ using Umbraco.Cms.Core.Models.Entities;
namespace Umbraco.Cms.Core.Models
{
+ public interface IRelationTypeWithIsDependency : IRelationType
+ {
+ ///
+ /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries.
+ ///
+ [DataMember]
+ bool IsDependency { get; set; }
+ }
+
public interface IRelationType : IEntity, IRememberBeingDirty
{
///
diff --git a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs
index 3cfcc89085..e424dabb93 100644
--- a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs
+++ b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core.ContentApps;
@@ -48,6 +48,11 @@ namespace Umbraco.Cms.Core.Models.Mapping
}
public IEnumerable GetContentApps(IUmbracoEntity source)
+ {
+ return GetContentAppsForEntity(source);
+ }
+
+ public IEnumerable GetContentAppsForEntity(IEntity source)
{
var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray();
diff --git a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs
index 3625e90a14..2f85a95953 100644
--- a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs
+++ b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs
@@ -133,7 +133,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
if (target is IContentTypeWithHistoryCleanup targetWithHistoryCleanup)
{
- targetWithHistoryCleanup.HistoryCleanup = source.HistoryCleanup;
+ MapHistoryCleanup(source, targetWithHistoryCleanup);
}
target.AllowedTemplates = source.AllowedTemplates
@@ -147,6 +147,34 @@ namespace Umbraco.Cms.Core.Models.Mapping
: _fileService.GetTemplate(source.DefaultTemplate));
}
+ private static void MapHistoryCleanup(DocumentTypeSave source, IContentTypeWithHistoryCleanup target)
+ {
+ // If source history cleanup is null we don't have to map all properties
+ if (source.HistoryCleanup is null)
+ {
+ target.HistoryCleanup = null;
+ return;
+ }
+
+ // We need to reset the dirty properties, because it is otherwise true, just because the json serializer has set properties
+ target.HistoryCleanup.ResetDirtyProperties(false);
+ if (target.HistoryCleanup.PreventCleanup != source.HistoryCleanup.PreventCleanup)
+ {
+ target.HistoryCleanup.PreventCleanup = source.HistoryCleanup.PreventCleanup;
+ }
+
+ if (target.HistoryCleanup.KeepAllVersionsNewerThanDays != source.HistoryCleanup.KeepAllVersionsNewerThanDays)
+ {
+ target.HistoryCleanup.KeepAllVersionsNewerThanDays = source.HistoryCleanup.KeepAllVersionsNewerThanDays;
+ }
+
+ if (target.HistoryCleanup.KeepLatestVersionPerDayForDays !=
+ source.HistoryCleanup.KeepLatestVersionPerDayForDays)
+ {
+ target.HistoryCleanup.KeepLatestVersionPerDayForDays = source.HistoryCleanup.KeepLatestVersionPerDayForDays;
+ }
+ }
+
// no MapAll - take care
private void Map(MediaTypeSave source, IMediaType target, MapperContext context)
{
@@ -196,7 +224,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
target.AllowCultureVariant = source.VariesByCulture();
target.AllowSegmentVariant = source.VariesBySegment();
- target.ContentApps = _commonMapper.GetContentApps(source);
+ target.ContentApps = _commonMapper.GetContentAppsForEntity(source);
//sync templates
target.AllowedTemplates = context.MapEnumerable(source.AllowedTemplates);
@@ -328,7 +356,10 @@ namespace Umbraco.Cms.Core.Models.Mapping
if (source.GroupId > 0)
{
- target.PropertyGroupId = new Lazy(() => source.GroupId, false);
+ if (target.PropertyGroupId?.Value != source.GroupId)
+ {
+ target.PropertyGroupId = new Lazy(() => source.GroupId, false);
+ }
}
target.Alias = source.Alias;
@@ -523,7 +554,15 @@ namespace Umbraco.Cms.Core.Models.Mapping
target.Thumbnail = source.Thumbnail;
target.AllowedAsRoot = source.AllowAsRoot;
- target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i));
+
+ bool allowedContentTypesUnchanged = target.AllowedContentTypes.Select(x => x.Id.Value)
+ .SequenceEqual(source.AllowedContentTypes);
+
+ if (allowedContentTypesUnchanged is false)
+ {
+ target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i));
+ }
+
if (!(target is IMemberType))
{
@@ -574,13 +613,21 @@ namespace Umbraco.Cms.Core.Models.Mapping
// ensure no duplicate alias, then assign the group properties collection
EnsureUniqueAliases(destProperties);
- destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
+ if (destGroup.PropertyTypes.SupportsPublishing != isPublishing || destGroup.PropertyTypes.SequenceEqual(destProperties) is false)
+ {
+ destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
+ }
+
destGroups.Add(destGroup);
}
// ensure no duplicate name, then assign the groups collection
EnsureUniqueAliases(destGroups);
- target.PropertyGroups = new PropertyGroupCollection(destGroups);
+
+ if (target.PropertyGroups.SequenceEqual(destGroups) is false)
+ {
+ target.PropertyGroups = new PropertyGroupCollection(destGroups);
+ }
// because the property groups collection was rebuilt, there is no need to remove
// the old groups - they are just gone and will be cleared by the repository
diff --git a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs
index 4c000f0173..b93f99c5c5 100644
--- a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs
+++ b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs
@@ -1,6 +1,7 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
+using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Services;
@@ -14,12 +15,20 @@ namespace Umbraco.Cms.Core.Models.Mapping
public class DictionaryMapDefinition : IMapDefinition
{
private readonly ILocalizationService _localizationService;
+ private readonly CommonMapper _commonMapper;
+ [Obsolete("Use the constructor with the CommonMapper")]
public DictionaryMapDefinition(ILocalizationService localizationService)
{
_localizationService = localizationService;
}
+ public DictionaryMapDefinition(ILocalizationService localizationService, CommonMapper commonMapper)
+ {
+ _localizationService = localizationService;
+ _commonMapper = commonMapper;
+ }
+
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define((source, context) => new EntityBasic(), Map);
@@ -44,6 +53,10 @@ namespace Umbraco.Cms.Core.Models.Mapping
target.Name = source.ItemKey;
target.ParentId = source.ParentId ?? Guid.Empty;
target.Udi = Udi.Create(Constants.UdiEntityType.DictionaryItem, source.Key);
+ if (_commonMapper != null)
+ {
+ target.ContentApps.AddRange(_commonMapper.GetContentAppsForEntity(source));
+ }
// build up the path to make it possible to set active item in tree
// TODO: check if there is a better way
diff --git a/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs
index 41caa526e2..2b333652b9 100644
--- a/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs
+++ b/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs
@@ -30,6 +30,11 @@ namespace Umbraco.Cms.Core.Models.Mapping
target.ChildObjectType = source.ChildObjectType;
target.Id = source.Id;
target.IsBidirectional = source.IsBidirectional;
+
+ if (source is IRelationTypeWithIsDependency sourceWithIsDependency)
+ {
+ target.IsDependency = sourceWithIsDependency.IsDependency;
+ }
target.Key = source.Key;
target.Name = source.Name;
target.Alias = source.Alias;
@@ -74,6 +79,11 @@ namespace Umbraco.Cms.Core.Models.Mapping
target.ChildObjectType = source.ChildObjectType;
target.Id = source.Id.TryConvertTo().Result;
target.IsBidirectional = source.IsBidirectional;
+ if (target is IRelationTypeWithIsDependency targetWithIsDependency)
+ {
+ targetWithIsDependency.IsDependency = source.IsDependency;
+ }
+
target.Key = source.Key;
target.Name = source.Name;
target.ParentObjectType = source.ParentObjectType;
diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs
new file mode 100644
index 0000000000..cebbc20951
--- /dev/null
+++ b/src/Umbraco.Core/Models/RelationItem.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Runtime.Serialization;
+using Umbraco.Cms.Core.Models.Entities;
+
+namespace Umbraco.Cms.Core.Models
+{
+ [DataContract(Name = "relationItem", Namespace = "")]
+ public class RelationItem
+ {
+ [DataMember(Name = "id")]
+ public int NodeId { get; set; }
+
+ [DataMember(Name = "key")]
+ public Guid NodeKey { get; set; }
+
+ [DataMember(Name = "name")]
+ public string NodeName { get; set; }
+
+ [DataMember(Name = "type")]
+ public string NodeType { get; set; }
+
+ [DataMember(Name = "udi")]
+ public Udi NodeUdi => Udi.Create(NodeType, NodeKey);
+
+ [DataMember(Name = "icon")]
+ public string ContentTypeIcon { get; set; }
+
+ [DataMember(Name = "alias")]
+ public string ContentTypeAlias { get; set; }
+
+ [DataMember(Name = "contentTypeName")]
+ public string ContentTypeName { get; set; }
+
+ [DataMember(Name = "relationTypeName")]
+ public string RelationTypeName { get; set; }
+
+ [DataMember(Name = "relationTypeIsBidirectional")]
+ public bool RelationTypeIsBidirectional { get; set; }
+
+ [DataMember(Name = "relationTypeIsDependency")]
+ public bool RelationTypeIsDependency { get; set; }
+
+ }
+}
diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs
index 2def0eb636..5de0aaac8f 100644
--- a/src/Umbraco.Core/Models/RelationType.cs
+++ b/src/Umbraco.Core/Models/RelationType.cs
@@ -9,20 +9,28 @@ namespace Umbraco.Cms.Core.Models
///
[Serializable]
[DataContract(IsReference = true)]
- public class RelationType : EntityBase, IRelationType
+ public class RelationType : EntityBase, IRelationType, IRelationTypeWithIsDependency
{
private string _name;
private string _alias;
private bool _isBidirectional;
+ private bool _isDependency;
private Guid? _parentObjectType;
private Guid? _childObjectType;
public RelationType(string alias, string name)
- : this(name: name, alias: alias, false, null, null)
+ : this(name: name, alias: alias, false, null, null, false)
{
}
+ [Obsolete("Use ctor with isDependency parameter")]
public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType)
+ :this(name,alias,isBidrectional, parentObjectType, childObjectType, false)
+ {
+
+ }
+
+ public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType, bool isDependency)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name));
@@ -32,6 +40,7 @@ namespace Umbraco.Cms.Core.Models
_name = name;
_alias = alias;
_isBidirectional = isBidrectional;
+ _isDependency = isDependency;
_parentObjectType = parentObjectType;
_childObjectType = childObjectType;
}
@@ -88,5 +97,11 @@ namespace Umbraco.Cms.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType));
}
+
+ public bool IsDependency
+ {
+ get => _isDependency;
+ set => SetPropertyValueAndDetectChanges(value, ref _isDependency, nameof(IsDependency));
+ }
}
}
diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs
index 6fc1db3208..a02fff017b 100644
--- a/src/Umbraco.Core/Packaging/PackagesRepository.cs
+++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs
@@ -5,7 +5,6 @@ using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
-using System.Text;
using System.Xml.Linq;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
@@ -33,7 +32,7 @@ namespace Umbraco.Cms.Core.Packaging
private readonly IEntityXmlSerializer _serializer;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly string _packageRepositoryFileName;
- private readonly string _mediaFolderPath;
+ private readonly string _createdPackagesFolderPath;
private readonly string _packagesFolderPath;
private readonly string _tempFolderPath;
private readonly PackageDefinitionXmlParser _parser;
@@ -93,7 +92,7 @@ namespace Umbraco.Cms.Core.Packaging
_tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles";
_packagesFolderPath = packagesFolderPath ?? Constants.SystemDirectories.Packages;
- _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages);
+ _createdPackagesFolderPath = mediaFolderPath ?? Constants.SystemDirectories.CreatedPackages;
_parser = new PackageDefinitionXmlParser();
_mediaService = mediaService;
@@ -250,15 +249,8 @@ namespace Umbraco.Cms.Core.Packaging
}
}
-
-
- var directoryName =
- _hostingEnvironment.MapPathWebRoot(Path.Combine(_mediaFolderPath, definition.Name.Replace(' ', '_')));
-
- if (Directory.Exists(directoryName) == false)
- {
- Directory.CreateDirectory(directoryName);
- }
+ var directoryName = _hostingEnvironment.MapPathContentRoot(Path.Combine(_createdPackagesFolderPath, definition.Name.Replace(' ', '_')));
+ Directory.CreateDirectory(directoryName);
var finalPackagePath = Path.Combine(directoryName, fileName);
@@ -276,14 +268,14 @@ namespace Umbraco.Cms.Core.Packaging
}
finally
{
- //Clean up
+ // Clean up
Directory.Delete(temporaryPath, true);
}
}
private void ValidatePackage(PackageDefinition definition)
{
- //ensure it's valid
+ // ensure it's valid
var context = new ValidationContext(definition, serviceProvider: null, items: null);
var results = new List();
var isValid = Validator.TryValidateObject(definition, context, results);
@@ -732,7 +724,6 @@ namespace Umbraco.Cms.Core.Packaging
private XDocument EnsureStorage(out string packagesFile)
{
var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath);
- //ensure it exists
Directory.CreateDirectory(packagesFolder);
packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile);
@@ -740,6 +731,8 @@ namespace Umbraco.Cms.Core.Packaging
{
var xml = new XDocument(new XElement("packages"));
xml.Save(packagesFile);
+
+ return xml;
}
var packagesXml = XDocument.Load(packagesFile);
@@ -749,9 +742,16 @@ namespace Umbraco.Cms.Core.Packaging
public void DeleteLocalRepositoryFiles()
{
var packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile);
- File.Delete(packagesFile);
+ if (File.Exists(packagesFile))
+ {
+ File.Delete(packagesFile);
+ }
+
var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath);
- Directory.Delete(packagesFolder);
+ if (Directory.Exists(packagesFolder))
+ {
+ Directory.Delete(packagesFolder);
+ }
}
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs
index cff64d4a79..c863ef4d8e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
diff --git a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs
new file mode 100644
index 0000000000..42746a9565
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models.Entities;
+
+namespace Umbraco.Cms.Core.Persistence.Repositories
+{
+ public interface ITrackedReferencesRepository
+ {
+ IEnumerable GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency,out long totalRecords);
+ IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency,out long totalRecords);
+ IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency,out long totalRecords);
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
index 6f9e1b6611..795d75c319 100644
--- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
@@ -6,7 +6,6 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Xml.Linq;
using Microsoft.Extensions.Logging;
-using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Editors;
@@ -158,17 +157,15 @@ namespace Umbraco.Cms.Core.PropertyEditors
/// ValueType was out of range.
internal Attempt TryConvertValueToCrlType(object value)
{
- // Ensure empty string values are converted to null
- if (value is string s && string.IsNullOrWhiteSpace(s))
+ // Ensure empty string and JSON values are converted to null
+ if (value is string stringValue && string.IsNullOrWhiteSpace(stringValue))
{
value = null;
}
-
- // Ensure JSON is serialized properly (without indentation or converted to null when empty)
- if (value is not null && ValueType.InvariantEquals(ValueTypes.Json))
+ else if (value is not string && ValueType.InvariantEquals(ValueTypes.Json))
{
+ // Only serialize value when it's not already a string
var jsonValue = _jsonSerializer.Serialize(value);
-
if (jsonValue.DetectIsEmptyJson())
{
value = null;
diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
index df18a79ffd..10256f3efa 100644
--- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
+++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
@@ -161,7 +161,7 @@ namespace Umbraco.Cms.Core.Routing
: DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture);
var defaultCulture = _localizationService.GetDefaultLanguageIsoCode();
- if (domainUri is not null || culture is null || culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase))
+ if (domainUri is not null || string.IsNullOrEmpty(culture) || culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase))
{
var url = AssembleUrl(domainUri, path, current, mode).ToString();
return UrlInfo.Url(url, culture);
diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
index 3853988aab..1580d3e181 100644
--- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
+++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
@@ -269,16 +269,10 @@ namespace Umbraco.Extensions
if (!pcr.HasPublishedContent())
{
- var logMsg = nameof(DetectCollisionAsync) +
+ const string 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);
- }
+
+ 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/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs
index 8f6813e7ba..ca3045a4de 100644
--- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs
+++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel;
using System.Security.Cryptography;
using System.Text;
@@ -144,7 +144,7 @@ namespace Umbraco.Cms.Core.Security
var saltBytes = Convert.FromBase64String(salt);
byte[] inArray;
- var hashAlgorithm = GetHashAlgorithm(algorithmType);
+ using var hashAlgorithm = GetHashAlgorithm(algorithmType);
var algorithm = hashAlgorithm as KeyedHashAlgorithm;
if (algorithm != null)
{
@@ -227,7 +227,7 @@ namespace Umbraco.Cms.Core.Security
/// The encoded password.
private string HashLegacySHA1Password(string password)
{
- var hashAlgorithm = GetLegacySHA1Algorithm(password);
+ using var hashAlgorithm = GetLegacySHA1Algorithm(password);
var hash = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password)));
return hash;
}
diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs
index ce00f774f8..4d0e977d38 100644
--- a/src/Umbraco.Core/Services/IRelationService.cs
+++ b/src/Umbraco.Core/Services/IRelationService.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
@@ -215,7 +215,7 @@ namespace Umbraco.Cms.Core.Services
///
///
///
- ///
+ /// An enumerable list of
IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
///
@@ -225,7 +225,7 @@ namespace Umbraco.Cms.Core.Services
///
///
///
- ///
+ /// An enumerable list of
IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
///
diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs
new file mode 100644
index 0000000000..eee8a324df
--- /dev/null
+++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs
@@ -0,0 +1,15 @@
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.Services
+{
+ public interface ITrackedReferencesService
+ {
+ PagedResult GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency);
+
+
+ PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency);
+
+
+ PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency);
+ }
+}
diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
index a24d47fe4b..30f68e59f1 100644
--- a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
+++ b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs
index d4cf07e9d0..68d858550b 100644
--- a/src/Umbraco.Core/Services/RelationService.cs
+++ b/src/Umbraco.Core/Services/RelationService.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
diff --git a/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs b/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs
new file mode 100644
index 0000000000..7fd0ee5a85
--- /dev/null
+++ b/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Umbraco.Cms.Core.Telemetry
+{
+ ///
+ /// Used to get and create the site identifier
+ ///
+ public interface ISiteIdentifierService
+ {
+
+ ///
+ /// Tries to get the site identifier
+ ///
+ /// True if success.
+ bool TryGetSiteIdentifier(out Guid siteIdentifier);
+
+ ///
+ /// Creates the site identifier and writes it to config.
+ ///
+ /// asd.
+ /// True if success.
+ bool TryCreateSiteIdentifier(out Guid createdGuid);
+
+ ///
+ /// Tries to get the site identifier or otherwise create it if it doesn't exist.
+ ///
+ /// The out parameter for the existing or create site identifier.
+ /// True if success.
+ bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier);
+ }
+}
diff --git a/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs b/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs
new file mode 100644
index 0000000000..b6e40665c1
--- /dev/null
+++ b/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs
@@ -0,0 +1,81 @@
+using System;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Core.Telemetry
+{
+ ///
+ internal class SiteIdentifierService : ISiteIdentifierService
+ {
+ private GlobalSettings _globalSettings;
+ private readonly IConfigManipulator _configManipulator;
+ private readonly ILogger _logger;
+
+ public SiteIdentifierService(
+ IOptionsMonitor optionsMonitor,
+ IConfigManipulator configManipulator,
+ ILogger logger)
+ {
+ _globalSettings = optionsMonitor.CurrentValue;
+ optionsMonitor.OnChange(globalSettings => _globalSettings = globalSettings);
+ _configManipulator = configManipulator;
+ _logger = logger;
+ }
+
+ ///
+ public bool TryGetSiteIdentifier(out Guid siteIdentifier)
+ {
+ // Parse telemetry string as a GUID & verify its a GUID and not some random string
+ // since users may have messed with or decided to empty the app setting or put in something random
+ if (Guid.TryParse(_globalSettings.Id, out var parsedTelemetryId) is false
+ || parsedTelemetryId == Guid.Empty)
+ {
+ siteIdentifier = Guid.Empty;
+ return false;
+ }
+
+ siteIdentifier = parsedTelemetryId;
+ return true;
+ }
+
+ ///
+ public bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier)
+ {
+ if (TryGetSiteIdentifier(out Guid existingId))
+ {
+ siteIdentifier = existingId;
+ return true;
+ }
+
+ if (TryCreateSiteIdentifier(out Guid createdId))
+ {
+ siteIdentifier = createdId;
+ return true;
+ }
+
+ siteIdentifier = Guid.Empty;
+ return false;
+ }
+
+ ///
+ public bool TryCreateSiteIdentifier(out Guid createdGuid)
+ {
+ createdGuid = Guid.NewGuid();
+
+ try
+ {
+ _configManipulator.SetGlobalId(createdGuid.ToString());
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Couldn't update config files with a telemetry site identifier");
+ createdGuid = Guid.Empty;
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Telemetry/TelemetryService.cs b/src/Umbraco.Core/Telemetry/TelemetryService.cs
index efc8e4401a..5dd8932f90 100644
--- a/src/Umbraco.Core/Telemetry/TelemetryService.cs
+++ b/src/Umbraco.Core/Telemetry/TelemetryService.cs
@@ -3,9 +3,7 @@
using System;
using System.Collections.Generic;
-using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration;
-using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Telemetry.Models;
using Umbraco.Extensions;
@@ -15,27 +13,27 @@ namespace Umbraco.Cms.Core.Telemetry
///
internal class TelemetryService : ITelemetryService
{
- private readonly IOptionsMonitor _globalSettings;
private readonly IManifestParser _manifestParser;
private readonly IUmbracoVersion _umbracoVersion;
+ private readonly ISiteIdentifierService _siteIdentifierService;
///
/// Initializes a new instance of the class.
///
public TelemetryService(
- IOptionsMonitor globalSettings,
IManifestParser manifestParser,
- IUmbracoVersion umbracoVersion)
+ IUmbracoVersion umbracoVersion,
+ ISiteIdentifierService siteIdentifierService)
{
_manifestParser = manifestParser;
_umbracoVersion = umbracoVersion;
- _globalSettings = globalSettings;
+ _siteIdentifierService = siteIdentifierService;
}
///
public bool TryGetTelemetryReportData(out TelemetryReportData telemetryReportData)
{
- if (TryGetTelemetryId(out Guid telemetryId) is false)
+ if (_siteIdentifierService.TryGetOrCreateSiteIdentifier(out Guid telemetryId) is false)
{
telemetryReportData = null;
return false;
@@ -45,28 +43,14 @@ namespace Umbraco.Cms.Core.Telemetry
{
Id = telemetryId,
Version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(),
- Packages = GetPackageTelemetry()
+ Packages = GetPackageTelemetry(),
};
return true;
}
- private bool TryGetTelemetryId(out Guid telemetryId)
- {
- // Parse telemetry string as a GUID & verify its a GUID and not some random string
- // since users may have messed with or decided to empty the app setting or put in something random
- if (Guid.TryParse(_globalSettings.CurrentValue.Id, out Guid parsedTelemetryId) is false)
- {
- telemetryId = Guid.Empty;
- return false;
- }
-
- telemetryId = parsedTelemetryId;
- return true;
- }
-
private IEnumerable GetPackageTelemetry()
{
- List packages = new ();
+ List packages = new();
IEnumerable manifests = _manifestParser.GetManifests();
foreach (PackageManifest manifest in manifests)
@@ -79,7 +63,7 @@ namespace Umbraco.Cms.Core.Telemetry
packages.Add(new PackageTelemetry
{
Name = manifest.PackageName,
- Version = manifest.Version ?? string.Empty
+ Version = manifest.Version ?? string.Empty,
});
}
diff --git a/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs b/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs
index ba3945c6e5..33230a296d 100644
--- a/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs
+++ b/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs
@@ -27,7 +27,11 @@ namespace Umbraco.Cms.Infrastructure.Examine
IOptionsMonitor indexOptions)
{
_hostingEnvironment = hostingEnvironment;
- _indexOptions = indexOptions.Get(index.Name);
+ if (indexOptions != null)
+ {
+ _indexOptions = indexOptions.Get(index.Name);
+
+ }
Index = index;
Logger = logger;
}
@@ -35,7 +39,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
public LuceneIndex Index { get; }
public ILogger Logger { get; }
-
+
public Attempt IsHealthy()
{
@@ -72,12 +76,12 @@ namespace Umbraco.Cms.Infrastructure.Examine
{
d[nameof(LuceneDirectoryIndexOptions.DirectoryFactory)] = _indexOptions.DirectoryFactory.GetType();
}
-
+
if (_indexOptions.IndexDeletionPolicy != null)
{
d[nameof(LuceneDirectoryIndexOptions.IndexDeletionPolicy)] = _indexOptions.IndexDeletionPolicy.GetType();
- }
-
+ }
+
}
return d;
diff --git a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs
index f9cc387929..d72f17d559 100644
--- a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs
+++ b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs
@@ -2,26 +2,45 @@ using System;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using Umbraco.Cms.Core.Configuration;
+using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Core.Configuration
{
public class JsonConfigManipulator : IConfigManipulator
{
private readonly IConfiguration _configuration;
+ private readonly ILogger _logger;
private readonly object _locker = new object();
- public JsonConfigManipulator(IConfiguration configuration) => _configuration = configuration;
+ [Obsolete]
+ public JsonConfigManipulator(IConfiguration configuration)
+ : this(configuration, StaticServiceProvider.Instance.GetRequiredService>())
+ { }
- public string UmbracoConnectionPath { get; } = $"ConnectionStrings:{ Cms.Core.Constants.System.UmbracoConnectionName}";
+ public JsonConfigManipulator(
+ IConfiguration configuration,
+ ILogger logger)
+ {
+ _configuration = configuration;
+ _logger = logger;
+ }
+
+ public string UmbracoConnectionPath { get; } = $"ConnectionStrings:{Cms.Core.Constants.System.UmbracoConnectionName}";
public void RemoveConnectionString()
{
var provider = GetJsonConfigurationProvider(UmbracoConnectionPath);
var json = GetJson(provider);
+ if (json is null)
+ {
+ _logger.LogWarning("Failed to remove connection string from JSON configuration.");
+ return;
+ }
RemoveJsonKey(json, UmbracoConnectionPath);
@@ -33,6 +52,11 @@ namespace Umbraco.Cms.Core.Configuration
var provider = GetJsonConfigurationProvider();
var json = GetJson(provider);
+ if (json is null)
+ {
+ _logger.LogWarning("Failed to save connection string in JSON configuration.");
+ return;
+ }
var item = GetConnectionItem(connectionString, providerName);
@@ -47,6 +71,11 @@ namespace Umbraco.Cms.Core.Configuration
var provider = GetJsonConfigurationProvider();
var json = GetJson(provider);
+ if (json is null)
+ {
+ _logger.LogWarning("Failed to save configuration key \"{Key}\" in JSON configuration.", key);
+ return;
+ }
JToken token = json;
foreach (var propertyName in key.Split(new[] { ':' }))
@@ -73,6 +102,11 @@ namespace Umbraco.Cms.Core.Configuration
var provider = GetJsonConfigurationProvider();
var json = GetJson(provider);
+ if (json is null)
+ {
+ _logger.LogWarning("Failed to save enabled/disabled state for redirect URL tracking in JSON configuration.");
+ return;
+ }
var item = GetDisableRedirectUrlItem(disable);
@@ -86,6 +120,11 @@ namespace Umbraco.Cms.Core.Configuration
var provider = GetJsonConfigurationProvider();
var json = GetJson(provider);
+ if (json is null)
+ {
+ _logger.LogWarning("Failed to save global identifier in JSON configuration.");
+ return;
+ }
var item = GetGlobalIdItem(id);
@@ -170,13 +209,20 @@ namespace Umbraco.Cms.Core.Configuration
{
var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path);
- using (var sw = new StreamWriter(jsonFilePath, false))
- using (var jsonTextWriter = new JsonTextWriter(sw)
+ try
{
- Formatting = Formatting.Indented,
- })
+ using (var sw = new StreamWriter(jsonFilePath, false))
+ using (var jsonTextWriter = new JsonTextWriter(sw)
+ {
+ Formatting = Formatting.Indented,
+ })
+ {
+ json?.WriteTo(jsonTextWriter);
+ }
+ }
+ catch (IOException exception)
{
- json?.WriteTo(jsonTextWriter);
+ _logger.LogWarning(exception, "JSON configuration could not be written: {path}", jsonFilePath);
}
}
}
@@ -186,19 +232,25 @@ namespace Umbraco.Cms.Core.Configuration
{
lock (_locker)
{
- if (provider.Source.FileProvider is PhysicalFileProvider physicalFileProvider)
+ if (provider.Source.FileProvider is not PhysicalFileProvider physicalFileProvider)
{
- var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path);
-
- var serializer = new JsonSerializer();
- using (var sr = new StreamReader(jsonFilePath))
- using (var jsonTextReader = new JsonTextReader(sr))
- {
- return serializer.Deserialize(jsonTextReader);
- }
+ return null;
}
- return null;
+ var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path);
+
+ try
+ {
+ var serializer = new JsonSerializer();
+ using var sr = new StreamReader(jsonFilePath);
+ using var jsonTextReader = new JsonTextReader(sr);
+ return serializer.Deserialize(jsonTextReader);
+ }
+ catch (IOException exception)
+ {
+ _logger.LogWarning(exception, "JSON configuration could not be read: {path}", jsonFilePath);
+ return null;
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs
index ccb515182e..fbb32671a1 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs
@@ -1,3 +1,4 @@
+using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -49,7 +50,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
ILogger logger = factory.GetRequiredService>();
GlobalSettings globalSettings = factory.GetRequiredService>().Value;
- var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPhysicalRootPath);
+ var rootPath = Path.IsPathRooted(globalSettings.UmbracoMediaPhysicalRootPath) ? globalSettings.UmbracoMediaPhysicalRootPath : hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPhysicalRootPath);
var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath);
return new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, rootPath, rootUrl);
});
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
index e0958bfdb7..d750eb15e0 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
@@ -1,7 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Install.InstallSteps;
using Umbraco.Cms.Core.Install.Models;
+using Umbraco.Cms.Core.Telemetry;
using Umbraco.Cms.Infrastructure.Install;
using Umbraco.Cms.Infrastructure.Install.InstallSteps;
using Umbraco.Extensions;
@@ -19,7 +22,12 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
- builder.Services.AddScoped();
+ builder.Services.AddScoped(provider =>
+ {
+ return new TelemetryIdentifierStep(
+ provider.GetRequiredService>(),
+ provider.GetRequiredService());
+ });
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs
index 511c09304d..734fcb5661 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs
@@ -48,6 +48,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
+ builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
index 80d4dd9b3f..5687fb467e 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
@@ -44,6 +44,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
@@ -84,7 +86,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
var pluginLangFolders = appPlugins.Exists == false
? Enumerable.Empty()
: appPlugins.GetDirectories()
- .SelectMany(x => x.GetDirectories("Lang", SearchOption.AllDirectories))
+ // Check for both Lang & lang to support case sensitive file systems.
+ .SelectMany(x => x.GetDirectories("?ang", SearchOption.AllDirectories).Where(x => x.Name.InvariantEquals("lang")))
.SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
diff --git a/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs b/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs
index 4222ac800e..b06248c79e 100644
--- a/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs
+++ b/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs
@@ -65,7 +65,7 @@ namespace Umbraco.Cms.Core.Events
var documentObjectType = Constants.ObjectTypes.Document;
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName;
- relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
+ relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType, false);
_relationService.Save(relationType);
}
@@ -123,7 +123,7 @@ namespace Umbraco.Cms.Core.Events
{
var documentObjectType = Constants.ObjectTypes.Document;
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName;
- relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
+ relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType, false);
_relationService.Save(relationType);
}
diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
index a27e0d9dee..7ea388ccb6 100644
--- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
@@ -7,6 +7,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;
@@ -101,7 +102,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
try
{
var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl);
- HttpClient httpClient = _httpClientFactory.CreateClient();
+ HttpClient httpClient = _httpClientFactory.CreateClient(Constants.HttpClients.IgnoreCertificateErrors);
_ = await httpClient.SendAsync(request);
}
catch (Exception ex)
diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs
index 2063bbc180..6f3996431e 100644
--- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs
@@ -24,6 +24,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
private TimeSpan _period;
private readonly TimeSpan _delay;
private Timer _timer;
+ private bool _disposedValue;
///
/// Initializes a new instance of the class.
@@ -84,7 +85,24 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
return Task.CompletedTask;
}
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ _timer?.Dispose();
+ }
+
+ _disposedValue = true;
+ }
+ }
+
///
- public void Dispose() => _timer?.Dispose();
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs
index 7591290bf4..cfce96281c 100644
--- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs
@@ -59,9 +59,6 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
// Send data to LIVE telemetry
s_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/");
- // Set a low timeout - no need to use a larger default timeout for this POST request
- s_httpClient.Timeout = new TimeSpan(0, 0, 1);
-
#if DEBUG
// Send data to DEBUG telemetry service
s_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/");
diff --git a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs
index f134d818be..a51f3f0384 100644
--- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Runtime;
@@ -12,6 +13,7 @@ using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Core.Web;
+using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Infrastructure.HostedServices
{
@@ -31,7 +33,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
private readonly IServerRoleAccessor _serverRegistrar;
private readonly IUmbracoContextFactory _umbracoContextFactory;
- ///
+ ///
/// Initializes a new instance of the class.
///
public ScheduledPublishing(
diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs
index 57354aafdb..43e2522efd 100644
--- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs
@@ -20,6 +20,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration
private readonly IRuntimeState _runtimeState;
private readonly IServerMessenger _messenger;
private readonly ILogger _logger;
+ private bool _disposedValue;
///
/// Initializes a new instance of the class.
@@ -54,5 +55,20 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration
return Task.CompletedTask;
}
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing && _messenger is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+
+ _disposedValue = true;
+ }
+
+ base.Dispose(disposing);
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs
index 77ca873638..0f98fb2924 100644
--- a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs
+++ b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Xml.XPath;
using Examine.Search;
@@ -8,31 +8,46 @@ using Umbraco.Cms.Core.Xml;
namespace Umbraco.Cms.Core
{
///
- /// Query methods used for accessing strongly typed content in templates
+ /// Query methods used for accessing strongly typed content in templates.
///
public interface IPublishedContentQuery
{
IPublishedContent Content(int id);
+
IPublishedContent Content(Guid id);
+
IPublishedContent Content(Udi id);
+
IPublishedContent Content(object id);
+
IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars);
+
IEnumerable Content(IEnumerable ids);
+
IEnumerable Content(IEnumerable ids);
IEnumerable Content(IEnumerable ids);
+
IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars);
+
IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars);
+
IEnumerable ContentAtRoot();
IPublishedContent Media(int id);
+
IPublishedContent Media(Guid id);
+
IPublishedContent Media(Udi id);
IPublishedContent Media(object id);
+
IEnumerable Media(IEnumerable ids);
+
IEnumerable Media(IEnumerable ids);
+
IEnumerable Media(IEnumerable ids);
+
IEnumerable MediaAtRoot();
///
@@ -44,7 +59,7 @@ namespace Umbraco.Cms.Core
/// The total amount of records.
/// The culture (defaults to a culture insensitive search).
/// The name of the index to search (defaults to ).
- /// The fields to load in the results of the search (defaults to all fields loaded).
+ /// This parameter is no longer used, because the results are loaded from the published snapshot using the single item ID field.
///
/// The search results.
///
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
index 25dadb0c85..b19802996b 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
@@ -421,21 +421,21 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
private void CreateRelationTypeData()
{
- var relationType = new RelationTypeDto { Id = 1, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = true, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyName };
+ var relationType = new RelationTypeDto { Id = 1, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = true, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, IsDependency = false};
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
- relationType = new RelationTypeDto { Id = 2, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName };
+ relationType = new RelationTypeDto { Id = 2, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName, IsDependency = false };
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
- relationType = new RelationTypeDto { Id = 3, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Media, ParentObjectType = Cms.Core.Constants.ObjectTypes.Media, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName };
+ relationType = new RelationTypeDto { Id = 3, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Media, ParentObjectType = Cms.Core.Constants.ObjectTypes.Media, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName, IsDependency = false };
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
- relationType = new RelationTypeDto { Id = 4, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaName };
+ relationType = new RelationTypeDto { Id = 4, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaName, IsDependency = true };
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
- relationType = new RelationTypeDto { Id = 5, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentName };
+ relationType = new RelationTypeDto { Id = 5, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentName, IsDependency = true };
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
index 5ccd19fdf1..d3a920a60b 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
@@ -243,6 +243,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
// to 8.17.0
To("{153865E9-7332-4C2A-9F9D-F20AEE078EC7}");
+ // Hack to support migration from 8.18
+ To("{03482BB0-CF13-475C-845E-ECB8319DBE3C}");
+
// This should be safe to execute again. We need it with a new name to ensure updates from all the following has executed this step.
// - 8.15.0 RC - Current state: {4695D0C9-0729-4976-985B-048D503665D8}
// - 8.15.0 Final - Current state: {5C424554-A32D-4852-8ED1-A13508187901}
@@ -280,6 +283,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
// TO 9.4.0
To("{DBBA1EA0-25A1-4863-90FB-5D306FB6F1E1}");
+ To("{DED98755-4059-41BB-ADBD-3FEAB12D1D7B}");
}
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexes.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexes.cs
index f350ed633c..db7f17eee3 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexes.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexes.cs
@@ -1,6 +1,10 @@
+using System;
using System.Collections.Generic;
using System.Linq;
+using NPoco;
using Umbraco.Cms.Core;
+using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
+using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
@@ -20,14 +24,14 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
{
// Before adding these indexes we need to remove duplicate data.
// Get all logins by latest
- var logins = Database.Fetch()
+ var logins = Database.Fetch()
.OrderByDescending(x => x.CreateDate)
.ToList();
var toDelete = new List();
// used to track duplicates so they can be removed
var keys = new HashSet<(string, string)>();
- foreach(ExternalLoginDto login in logins)
+ foreach(ExternalLoginTokenTable.LegacyExternalLoginDto login in logins)
{
if (!keys.Add((login.ProviderKey, login.LoginProvider)))
{
@@ -37,16 +41,16 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
}
if (toDelete.Count > 0)
{
- Database.DeleteMany().Where(x => toDelete.Contains(x.Id)).Execute();
- }
+ Database.DeleteMany().Where(x => toDelete.Contains(x.Id)).Execute();
+ }
- var indexName1 = "IX_" + ExternalLoginDto.TableName + "_LoginProvider";
+ var indexName1 = "IX_" + ExternalLoginTokenTable.LegacyExternalLoginDto.TableName + "_LoginProvider";
if (!IndexExists(indexName1))
{
Create
.Index(indexName1)
- .OnTable(ExternalLoginDto.TableName)
+ .OnTable(ExternalLoginTokenTable.LegacyExternalLoginDto.TableName)
.OnColumn("loginProvider")
.Ascending()
.WithOptions()
@@ -56,13 +60,13 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
.Do();
}
- var indexName2 = "IX_" + ExternalLoginDto.TableName + "_ProviderKey";
+ var indexName2 = "IX_" + ExternalLoginTokenTable.LegacyExternalLoginDto.TableName + "_ProviderKey";
if (!IndexExists(indexName2))
{
Create
.Index(indexName2)
- .OnTable(ExternalLoginDto.TableName)
+ .OnTable(ExternalLoginTokenTable.LegacyExternalLoginDto.TableName)
.OnColumn("loginProvider").Ascending()
.OnColumn("providerKey").Ascending()
.WithOptions()
@@ -70,5 +74,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
.Do();
}
}
+
+
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexesFixup.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexesFixup.cs
index 5efb914eb7..2c77b301ce 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexesFixup.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexesFixup.cs
@@ -14,29 +14,29 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
protected override void Migrate()
{
- var indexName1 = "IX_" + ExternalLoginDto.TableName + "_LoginProvider";
- var indexName2 = "IX_" + ExternalLoginDto.TableName + "_ProviderKey";
+ var indexName1 = "IX_" + ExternalLoginTokenTable.LegacyExternalLoginDto.TableName + "_LoginProvider";
+ var indexName2 = "IX_" + ExternalLoginTokenTable.LegacyExternalLoginDto.TableName + "_ProviderKey";
if (IndexExists(indexName1))
{
// drop it since the previous migration index was wrong, and we
// need to modify a column that belons to it
- Delete.Index(indexName1).OnTable(ExternalLoginDto.TableName).Do();
+ Delete.Index(indexName1).OnTable(ExternalLoginTokenTable.LegacyExternalLoginDto.TableName).Do();
}
if (IndexExists(indexName2))
{
// drop since it's using a column we're about to modify
- Delete.Index(indexName2).OnTable(ExternalLoginDto.TableName).Do();
+ Delete.Index(indexName2).OnTable(ExternalLoginTokenTable.LegacyExternalLoginDto.TableName).Do();
}
// then fixup the length of the loginProvider column
- AlterColumn(ExternalLoginDto.TableName, "loginProvider");
+ AlterColumn(ExternalLoginTokenTable.LegacyExternalLoginDto.TableName, "loginProvider");
// create it with the correct definition
Create
.Index(indexName1)
- .OnTable(ExternalLoginDto.TableName)
+ .OnTable(ExternalLoginTokenTable.LegacyExternalLoginDto.TableName)
.OnColumn("loginProvider").Ascending()
.OnColumn("userId").Ascending()
.WithOptions()
@@ -48,9 +48,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
// re-create the original
Create
.Index(indexName2)
- .OnTable(ExternalLoginDto.TableName)
+ .OnTable(ExternalLoginTokenTable.LegacyExternalLoginDto.TableName)
.OnColumn("loginProvider").Ascending()
- .OnColumn("providerKey").Ascending()
+ .OnColumn("providerKey").Ascending()
.WithOptions()
.NonClustered()
.Do();
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTokenTable.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTokenTable.cs
index 8dd43f1834..851d986c7c 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTokenTable.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTokenTable.cs
@@ -1,10 +1,14 @@
+using System;
using System.Collections.Generic;
+using NPoco;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
+using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
{
-
public class ExternalLoginTokenTable : MigrationBase
{
public ExternalLoginTokenTable(IMigrationContext context)
@@ -13,7 +17,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
}
///
- /// Adds new External Login token table
+ /// Adds new External Login token table
///
protected override void Migrate()
{
@@ -25,5 +29,53 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
Create.Table().Do();
}
+
+ [TableName(TableName)]
+ [ExplicitColumns]
+ [PrimaryKey("Id")]
+ internal class LegacyExternalLoginDto
+ {
+ public const string TableName = Constants.DatabaseSchema.Tables.ExternalLogin;
+
+ [Column("id")] [PrimaryKeyColumn] public int Id { get; set; }
+
+ [Obsolete(
+ "This only exists to ensure you can upgrade using external logins from umbraco version where this was used to the new where it is not used")]
+ [Column("userId")]
+ public int? UserId { get; set; }
+
+
+ ///
+ /// Used to store the name of the provider (i.e. Facebook, Google)
+ ///
+ [Column("loginProvider")]
+ [Length(400)]
+ [NullSetting(NullSetting = NullSettings.NotNull)]
+ [Index(IndexTypes.UniqueNonClustered, ForColumns = "loginProvider,userOrMemberKey",
+ Name = "IX_" + TableName + "_LoginProvider")]
+ public string LoginProvider { get; set; }
+
+ ///
+ /// Stores the key the provider uses to lookup the login
+ ///
+ [Column("providerKey")]
+ [Length(4000)]
+ [NullSetting(NullSetting = NullSettings.NotNull)]
+ [Index(IndexTypes.NonClustered, ForColumns = "loginProvider,providerKey",
+ Name = "IX_" + TableName + "_ProviderKey")]
+ public string ProviderKey { get; set; }
+
+ [Column("createDate")]
+ [Constraint(Default = SystemMethods.CurrentDateTime)]
+ public DateTime CreateDate { get; set; }
+
+ ///
+ /// Used to store any arbitrary data for the user and external provider - like user tokens returned from the provider
+ ///
+ [Column("userData")]
+ [NullSetting(NullSetting = NullSettings.Null)]
+ [SpecialDbType(SpecialDbTypes.NTEXT)]
+ public string UserData { get; set; }
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_4_0/UpdateRelationTypesToHandleDependencies.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_4_0/UpdateRelationTypesToHandleDependencies.cs
new file mode 100644
index 0000000000..1c8fe7ed72
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_4_0/UpdateRelationTypesToHandleDependencies.cs
@@ -0,0 +1,34 @@
+using System.Linq;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+using Umbraco.Extensions;
+
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_4_0
+{
+ internal class UpdateRelationTypesToHandleDependencies : MigrationBase
+ {
+ public UpdateRelationTypesToHandleDependencies(IMigrationContext context)
+ : base(context)
+ {
+ }
+
+ protected override void Migrate()
+ {
+ var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
+
+ AddColumnIfNotExists(columns, "isDependency");
+
+ var aliasesWithDependencies = new[]
+ {
+ Core.Constants.Conventions.RelationTypes.RelatedDocumentAlias,
+ Core.Constants.Conventions.RelationTypes.RelatedMediaAlias
+ };
+
+ Database.Execute(
+ Sql()
+ .Update(u => u.Set(x => x.IsDependency, true))
+ .WhereIn(x => x.Alias, aliasesWithDependencies));
+
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/RelationTypeDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/RelationTypeDto.cs
index 50d7960ff8..388fa58941 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/RelationTypeDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/RelationTypeDto.cs
@@ -40,5 +40,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
[Length(100)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")]
public string Alias { get; set; }
+
+ [Constraint(Default = "0")]
+ [Column("isDependency")]
+ public bool IsDependency { get; set; }
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/RelationTypeFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/RelationTypeFactory.cs
index 51f5261199..93cb74cd74 100644
--- a/src/Umbraco.Infrastructure/Persistence/Factories/RelationTypeFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Factories/RelationTypeFactory.cs
@@ -9,7 +9,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories
public static IRelationType BuildEntity(RelationTypeDto dto)
{
- var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ParentObjectType, dto.ChildObjectType);
+ var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ParentObjectType, dto.ChildObjectType, dto.IsDependency);
try
{
@@ -30,11 +30,17 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories
public static RelationTypeDto BuildDto(IRelationType entity)
{
+ var isDependency = false;
+ if (entity is IRelationTypeWithIsDependency relationTypeWithIsDependency)
+ {
+ isDependency = relationTypeWithIsDependency.IsDependency;
+ }
var dto = new RelationTypeDto
{
Alias = entity.Alias,
ChildObjectType = entity.ChildObjectType,
Dual = entity.IsBidirectional,
+ IsDependency = isDependency,
Name = entity.Name,
ParentObjectType = entity.ParentObjectType,
UniqueId = entity.Key
@@ -47,6 +53,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories
return dto;
}
+
+
#endregion
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs
index 965a659631..732563fef7 100644
--- a/src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs
@@ -22,6 +22,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
DefineMap(nameof(RelationType.Alias), nameof(RelationTypeDto.Alias));
DefineMap(nameof(RelationType.ChildObjectType), nameof(RelationTypeDto.ChildObjectType));
DefineMap(nameof(RelationType.IsBidirectional), nameof(RelationTypeDto.Dual));
+ DefineMap(nameof(RelationType.IsDependency), nameof(RelationTypeDto.IsDependency));
DefineMap(nameof(RelationType.Name), nameof(RelationTypeDto.Name));
DefineMap(nameof(RelationType.ParentObjectType), nameof(RelationTypeDto.ParentObjectType));
}
diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs
index 05f15f7372..6b7c34dc15 100644
--- a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs
+++ b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs
@@ -72,7 +72,27 @@ namespace Umbraco.Extensions
/// The Sql statement.
public static Sql WhereIn(this Sql sql, Expression> field, Sql values)
{
- return sql.WhereIn(field, values, false);
+ return WhereIn(sql, field, values, false, null);
+ }
+
+ public static Sql WhereIn(this Sql sql, Expression> field, Sql values, string tableAlias)
+ {
+ return sql.WhereIn(field, values, false, tableAlias);
+ }
+
+
+ public static Sql WhereLike(this Sql sql, Expression> fieldSelector, Sql valuesSql)
+ {
+ var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector);
+ sql.Where(fieldName + " LIKE (" + valuesSql.SQL + ")", valuesSql.Arguments);
+ return sql;
+ }
+
+ public static Sql WhereLike(this Sql sql, Expression> fieldSelector, string likeValue)
+ {
+ var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector);
+ sql.Where(fieldName + " LIKE ('" + likeValue + "')");
+ return sql;
}
///
@@ -130,7 +150,12 @@ namespace Umbraco.Extensions
private static Sql WhereIn(this Sql sql, Expression> fieldSelector, Sql valuesSql, bool not)
{
- var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector);
+ return WhereIn(sql, fieldSelector, valuesSql, not, null);
+ }
+
+ private static Sql WhereIn(this Sql sql, Expression> fieldSelector, Sql valuesSql, bool not, string tableAlias)
+ {
+ var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector, tableAlias);
sql.Where(fieldName + (not ? " NOT" : "") +" IN (" + valuesSql.SQL + ")", valuesSql.Arguments);
return sql;
}
@@ -252,7 +277,7 @@ namespace Umbraco.Extensions
/// The Sql statement.
public static Sql OrderByDescending(this Sql sql, Expression> field)
{
- return sql.OrderBy("(" + sql.SqlContext.SqlSyntax.GetFieldName(field) + ") DESC");
+ return sql.OrderByDescending(sql.SqlContext.SqlSyntax.GetFieldName(field));
}
///
@@ -268,7 +293,7 @@ namespace Umbraco.Extensions
var columns = fields.Length == 0
? sql.GetColumns(withAlias: false)
: fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
- return sql.OrderBy(columns.Select(x => x + " DESC"));
+ return sql.OrderByDescending(columns);
}
///
diff --git a/src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs b/src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs
index d64c0b873e..e17299649d 100644
--- a/src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs
@@ -468,7 +468,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Querying
case nameof(StringExtensions.InvariantStartsWith):
case nameof(StringExtensions.InvariantEndsWith):
case nameof(StringExtensions.InvariantContains):
- case nameof(StringExtensions.InvariantEquals):
+ case nameof(StringExtensions.InvariantEquals):
string compareValue;
@@ -629,7 +629,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Querying
if (m.Arguments.Count == 2)
{
var n1 = Visit(m.Arguments[0]);
- var f = m.Arguments[2];
+ var f = m.Arguments[1];
if (!(f is Expression> fl))
throw new NotSupportedException("Expression is not a proper lambda.");
var ff = fl.Compile();
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
index 462ae0a64e..438d5c1a1a 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
@@ -39,7 +39,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
private readonly IMacroService _macroService;
private readonly IContentTypeService _contentTypeService;
private readonly string _tempFolderPath;
- private readonly string _mediaFolderPath;
+ private readonly string _createdPackagesFolderPath;
///
/// Initializes a new instance of the class.
@@ -76,9 +76,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
_macroService = macroService;
_contentTypeService = contentTypeService;
_xmlParser = new PackageDefinitionXmlParser();
- _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages);
- _tempFolderPath =
- tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles";
+ _createdPackagesFolderPath = mediaFolderPath ?? Constants.SystemDirectories.CreatedPackages;
+ _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData + "/PackageFiles";
}
public IEnumerable GetAll()
@@ -192,17 +191,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
public string ExportPackage(PackageDefinition definition)
{
-
// Ensure it's valid
ValidatePackage(definition);
// Create a folder for building this package
- var temporaryPath =
- _hostingEnvironment.MapPathContentRoot(_tempFolderPath.EnsureEndsWith('/') + Guid.NewGuid());
- if (Directory.Exists(temporaryPath) == false)
- {
- Directory.CreateDirectory(temporaryPath);
- }
+ var temporaryPath = _hostingEnvironment.MapPathContentRoot(Path.Combine(_tempFolderPath, Guid.NewGuid().ToString()));
+ Directory.CreateDirectory(temporaryPath);
try
{
@@ -218,8 +212,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
PackageTemplates(definition, root);
PackageStylesheets(definition, root);
PackageStaticFiles(definition.Scripts, root, "Scripts", "Script", _fileSystems.ScriptsFileSystem);
- PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View",
- _fileSystems.PartialViewsFileSystem);
+ PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View", _fileSystems.PartialViewsFileSystem);
PackageMacros(definition, root);
PackageDictionaryItems(definition, root);
PackageLanguages(definition, root);
@@ -265,27 +258,25 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
}
}
- var directoryName =
- _hostingEnvironment.MapPathWebRoot(
- Path.Combine(_mediaFolderPath, definition.Name.Replace(' ', '_')));
-
- if (Directory.Exists(directoryName) == false)
- {
- Directory.CreateDirectory(directoryName);
- }
+ var directoryName = _hostingEnvironment.MapPathContentRoot(Path.Combine(_createdPackagesFolderPath, definition.Name.Replace(' ', '_')));
+ Directory.CreateDirectory(directoryName);
var finalPackagePath = Path.Combine(directoryName, fileName);
- if (File.Exists(finalPackagePath))
+ // Clean existing files
+ foreach (var packagePath in new[]
{
- File.Delete(finalPackagePath);
- }
-
- if (File.Exists(finalPackagePath.Replace("zip", "xml")))
- {
- File.Delete(finalPackagePath.Replace("zip", "xml"));
+ definition.PackagePath,
+ finalPackagePath
+ })
+ {
+ if (File.Exists(packagePath))
+ {
+ File.Delete(packagePath);
+ }
}
+ // Move to final package path
File.Move(tempPackagePath, finalPackagePath);
definition.PackagePath = finalPackagePath;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
index 718ea04f63..733a85b55b 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Linq.Expressions;
using Microsoft.Extensions.Logging;
using NPoco;
using Umbraco.Cms.Core;
@@ -171,6 +172,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
}
public IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
+ {
+ return GetPagedParentEntitiesByChildId(childId, pageIndex, pageSize, out totalRecords, new int[0], entityTypes);
+ }
+
+ public IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, int[] relationTypes, params Guid[] entityTypes)
{
// var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }
// we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data
@@ -184,10 +190,36 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
sql.Where(rel => rel.ChildId == childId);
sql.Where((rel, node) => rel.ParentId == childId || node.NodeId != childId);
+
+ if (relationTypes != null && relationTypes.Any())
+ {
+ sql.WhereIn(rel => rel.RelationType, relationTypes);
+ }
+ });
+ }
+
+ public IEnumerable GetPagedParentEntitiesByChildIds(int[] childIds, long pageIndex, int pageSize, out long totalRecords, int[] relationTypes, params Guid[] entityTypes)
+ {
+ return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
+ {
+ SqlJoinRelations(sql);
+
+ sql.WhereIn(rel => rel.ChildId, childIds);
+ sql.WhereAny(s => s.WhereIn(rel => rel.ParentId, childIds), s => s.WhereNotIn(node => node.NodeId, childIds));
+
+ if (relationTypes != null && relationTypes.Any())
+ {
+ sql.WhereIn(rel => rel.RelationType, relationTypes);
+ }
});
}
public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
+ {
+ return GetPagedChildEntitiesByParentId(parentId, pageIndex, pageSize, out totalRecords, new int[0], entityTypes);
+ }
+
+ public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, int[] relationTypes, params Guid[] entityTypes)
{
// var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }
// we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data
@@ -201,9 +233,29 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
sql.Where(rel => rel.ParentId == parentId);
sql.Where((rel, node) => rel.ChildId == parentId || node.NodeId != parentId);
+
+ if (relationTypes != null && relationTypes.Any())
+ {
+ sql.WhereIn(rel => rel.RelationType, relationTypes);
+ }
});
}
+ public IEnumerable GetPagedEntitiesForItemsInRelation(int[] itemIds, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
+ {
+ return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
+ {
+ SqlJoinRelations(sql);
+
+ sql.WhereIn(rel => rel.ChildId, itemIds);
+ sql.Where((rel, node) => rel.ChildId == node.NodeId);
+ sql.Where(type => type.IsDependency);
+ });
+ }
+
+
+
+
public void Save(IEnumerable relations)
{
foreach (var hasIdentityGroup in relations.GroupBy(r => r.HasIdentity))
@@ -399,4 +451,42 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
sql.OrderByDescending(orderBy);
}
}
+
+ internal class RelationItemDto
+ {
+ [Column(Name = "nodeId")]
+ public int ChildNodeId { get; set; }
+
+ [Column(Name = "nodeKey")]
+ public Guid ChildNodeKey { get; set; }
+
+ [Column(Name = "nodeName")]
+ public string ChildNodeName { get; set; }
+
+ [Column(Name = "nodeObjectType")]
+ public Guid ChildNodeObjectType { get; set; }
+
+ [Column(Name = "contentTypeIcon")]
+ public string ChildContentTypeIcon { get; set; }
+
+ [Column(Name = "contentTypeAlias")]
+ public string ChildContentTypeAlias { get; set; }
+
+ [Column(Name = "contentTypeName")]
+ public string ChildContentTypeName { get; set; }
+
+
+
+ [Column(Name = "relationTypeName")]
+ public string RelationTypeName { get; set; }
+
+ [Column(Name = "relationTypeAlias")]
+ public string RelationTypeAlias { get; set; }
+
+ [Column(Name = "relationTypeIsDependency")]
+ public bool RelationTypeIsDependency { get; set; }
+
+ [Column(Name = "relationTypeIsBidirectional")]
+ public bool RelationTypeIsBidirectional { get; set; }
+ }
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimilarNodeName.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimilarNodeName.cs
index 116ec356b7..9146dce196 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimilarNodeName.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimilarNodeName.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
@@ -51,7 +51,22 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
}
}
- // no suffix - name without suffix does NOT exist, AND name with suffix does NOT exist
+ // no suffix - name without suffix does NOT exist - we can just use the name without suffix.
+ if (!model.Suffix.HasValue && !items.SimpleNameExists(model.Text))
+ {
+ model.Suffix = StructuredName.NO_SUFFIX;
+
+ return model.FullName;
+ }
+
+ // suffix - name with suffix does NOT exist
+ // We can just return the full name as it is as there's no conflict.
+ if (model.Suffix.HasValue && !items.SimpleNameExists(model.FullName))
+ {
+ return model.FullName;
+ }
+
+ // no suffix - name without suffix does NOT exist, AND name with suffix does NOT exist
if (!model.Suffix.HasValue && !items.SimpleNameExists(model.Text) && !items.SuffixedNameExists())
{
model.Suffix = StructuredName.NO_SUFFIX;
@@ -163,7 +178,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
internal static readonly uint? NO_SUFFIX = default;
internal string Text { get; set; }
- internal uint ? Suffix { get; set; }
+ internal uint? Suffix { get; set; }
public string FullName
{
get
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
new file mode 100644
index 0000000000..0d33105c4b
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NPoco;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Persistence;
+using Umbraco.Cms.Core.Persistence.Repositories;
+using Umbraco.Cms.Core.Scoping;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+using Umbraco.Cms.Infrastructure.Scoping;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
+{
+ internal class TrackedReferencesRepository : ITrackedReferencesRepository
+ {
+ private readonly IScopeAccessor _scopeAccessor;
+
+ public TrackedReferencesRepository(IScopeAccessor scopeAccessor)
+ {
+ _scopeAccessor = scopeAccessor;
+ }
+
+ public IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize,
+ bool filterMustBeIsDependency, out long totalRecords)
+ {
+ var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select(
+ "[pn].[id] as nodeId",
+ "[pn].[uniqueId] as nodeKey",
+ "[pn].[text] as nodeName",
+ "[pn].[nodeObjectType] as nodeObjectType",
+ "[ct].[icon] as contentTypeIcon",
+ "[ct].[alias] as contentTypeAlias",
+ "[ctn].[text] as contentTypeName",
+ "[umbracoRelationType].[alias] as relationTypeAlias",
+ "[umbracoRelationType].[name] as relationTypeName",
+ "[umbracoRelationType].[isDependency] as relationTypeIsDependency",
+ "[umbracoRelationType].[dual] as relationTypeIsBidirectional")
+ .From("r")
+ .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType")
+ .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" )
+ .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" )
+ .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"pn", aliasRight:"c")
+ .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct")
+ .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn");
+
+ if (ids.Any())
+ {
+ sql = sql.Where(x => ids.Contains(x.NodeId), "pn");
+ }
+
+ if (filterMustBeIsDependency)
+ {
+ sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType");
+ }
+
+ // Ordering is required for paging
+ sql = sql.OrderBy(x => x.Alias);
+
+ var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql);
+ totalRecords = Convert.ToInt32(pagedResult.TotalItems);
+
+ return pagedResult.Items.Select(MapDtoToEntity);
+ }
+
+ public IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency,
+ out long totalRecords)
+ {
+ var syntax = _scopeAccessor.AmbientScope.Database.SqlContext.SqlSyntax;
+
+ // Gets the path of the parent with ",%" added
+ var subsubQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql()
+ .Select(syntax.GetConcat("[node].[path]", "',%'"))
+ .From("node")
+ .Where(x => x.NodeId == parentId, "node");
+
+
+ // Gets the descendants of the parent node
+ Sql subQuery;
+
+ if (_scopeAccessor.AmbientScope.Database.DatabaseType.IsSqlCe())
+ {
+ // SqlCE do not support nested selects that returns a scalar. So we need to do this in multiple queries
+
+ var pathForLike = _scopeAccessor.AmbientScope.Database.ExecuteScalar(subsubQuery);
+
+ subQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql()
+ .Select(x => x.NodeId)
+ .From()
+ .WhereLike(x => x.Path, pathForLike);
+ }
+ else
+ {
+ subQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql()
+ .Select(x => x.NodeId)
+ .From()
+ .WhereLike(x => x.Path, subsubQuery);
+ }
+
+
+
+ // Get all relations where parent is in the sub query
+ var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select(
+ "[pn].[id] as nodeId",
+ "[pn].[uniqueId] as nodeKey",
+ "[pn].[text] as nodeName",
+ "[pn].[nodeObjectType] as nodeObjectType",
+ "[ct].[icon] as contentTypeIcon",
+ "[ct].[alias] as contentTypeAlias",
+ "[ctn].[text] as contentTypeName",
+ "[umbracoRelationType].[alias] as relationTypeAlias",
+ "[umbracoRelationType].[name] as relationTypeName",
+ "[umbracoRelationType].[isDependency] as relationTypeIsDependency",
+ "[umbracoRelationType].[dual] as relationTypeIsBidirectional")
+ .From("r")
+ .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType")
+ .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" )
+ .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" )
+ .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"pn", aliasRight:"c")
+ .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct")
+ .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn")
+ .WhereIn((System.Linq.Expressions.Expression>)(x => x.NodeId), subQuery, "pn");
+ if (filterMustBeIsDependency)
+ {
+ sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType");
+ }
+ // Ordering is required for paging
+ sql = sql.OrderBy(x => x.Alias);
+
+ var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql);
+ totalRecords = Convert.ToInt32(pagedResult.TotalItems);
+
+ return pagedResult.Items.Select(MapDtoToEntity);
+ }
+
+ public IEnumerable GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords)
+ {
+ var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select(
+ "[cn].[id] as nodeId",
+ "[cn].[uniqueId] as nodeKey",
+ "[cn].[text] as nodeName",
+ "[cn].[nodeObjectType] as nodeObjectType",
+ "[ct].[icon] as contentTypeIcon",
+ "[ct].[alias] as contentTypeAlias",
+ "[ctn].[text] as contentTypeName",
+ "[umbracoRelationType].[alias] as relationTypeAlias",
+ "[umbracoRelationType].[name] as relationTypeName",
+ "[umbracoRelationType].[isDependency] as relationTypeIsDependency",
+ "[umbracoRelationType].[dual] as relationTypeIsBidirectional")
+ .From("r")
+ .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType")
+ .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" )
+ .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" )
+ .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"cn", aliasRight:"c")
+ .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct")
+ .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn");
+
+ if (ids.Any())
+ {
+ sql = sql.Where(x => ids.Contains(x.NodeId), "pn");
+ }
+
+ if (filterMustBeIsDependency)
+ {
+ sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType");
+ }
+
+ // Ordering is required for paging
+ sql = sql.OrderBy(x => x.Alias);
+
+ var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql);
+ totalRecords = Convert.ToInt32(pagedResult.TotalItems);
+
+ return pagedResult.Items.Select(MapDtoToEntity);
+ }
+
+ private RelationItem MapDtoToEntity(RelationItemDto dto)
+ {
+ var type = ObjectTypes.GetUdiType(dto.ChildNodeObjectType);
+ return new RelationItem()
+ {
+ NodeId = dto.ChildNodeId,
+ NodeKey = dto.ChildNodeKey,
+ NodeType = ObjectTypes.GetUdiType(dto.ChildNodeObjectType),
+ NodeName = dto.ChildNodeName,
+ RelationTypeName = dto.RelationTypeName,
+ RelationTypeIsBidirectional = dto.RelationTypeIsBidirectional,
+ RelationTypeIsDependency = dto.RelationTypeIsDependency,
+ ContentTypeAlias = dto.ChildContentTypeAlias,
+ ContentTypeIcon = dto.ChildContentTypeIcon,
+ ContentTypeName = dto.ChildContentTypeName,
+ };
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs
index d8119f919c..d0afe53540 100644
--- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs
+++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs
@@ -17,23 +17,25 @@ using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Infrastructure
{
///
- /// A class used to query for published content, media items
+ /// A class used to query for published content, media items
///
+ ///
public class PublishedContentQuery : IPublishedContentQuery
{
private readonly IExamineManager _examineManager;
private readonly IPublishedSnapshot _publishedSnapshot;
private readonly IVariationContextAccessor _variationContextAccessor;
+ private static readonly HashSet s_itemIdFieldNameHashSet =
+ new HashSet() { ExamineFieldNames.ItemIdFieldName };
+
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public PublishedContentQuery(IPublishedSnapshot publishedSnapshot,
- IVariationContextAccessor variationContextAccessor, IExamineManager examineManager)
+ public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor, IExamineManager examineManager)
{
_publishedSnapshot = publishedSnapshot ?? throw new ArgumentNullException(nameof(publishedSnapshot));
- _variationContextAccessor = variationContextAccessor ??
- throw new ArgumentNullException(nameof(variationContextAccessor));
+ _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
_examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager));
}
@@ -72,6 +74,7 @@ namespace Umbraco.Cms.Infrastructure
return false;
}
}
+
private static bool ConvertIdObjectToUdi(object id, out Udi guidId)
{
switch (id)
@@ -93,160 +96,148 @@ namespace Umbraco.Cms.Infrastructure
#region Content
- public IPublishedContent Content(int id) => ItemById(id, _publishedSnapshot.Content);
+ public IPublishedContent Content(int id)
+ => ItemById(id, _publishedSnapshot.Content);
- public IPublishedContent Content(Guid id) => ItemById(id, _publishedSnapshot.Content);
+ public IPublishedContent Content(Guid id)
+ => ItemById(id, _publishedSnapshot.Content);
public IPublishedContent Content(Udi id)
- {
- if (!(id is GuidUdi udi)) return null;
- return ItemById(udi.Guid, _publishedSnapshot.Content);
- }
+ => id is GuidUdi udi ? ItemById(udi.Guid, _publishedSnapshot.Content) : null;
public IPublishedContent Content(object id)
{
if (ConvertIdObjectToInt(id, out var intId))
+ {
return Content(intId);
+ }
+
if (ConvertIdObjectToGuid(id, out var guidId))
+ {
return Content(guidId);
+ }
+
if (ConvertIdObjectToUdi(id, out var udiId))
+ {
return Content(udiId);
+ }
+
return null;
}
- public IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars) =>
- ItemByXPath(xpath, vars, _publishedSnapshot.Content);
+ public IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars)
+ => ItemByXPath(xpath, vars, _publishedSnapshot.Content);
- public IEnumerable Content(IEnumerable ids) =>
- ItemsByIds(_publishedSnapshot.Content, ids);
+ public IEnumerable Content(IEnumerable ids)
+ => ItemsByIds(_publishedSnapshot.Content, ids);
- public IEnumerable Content(IEnumerable ids) =>
- ItemsByIds(_publishedSnapshot.Content, ids);
+ public IEnumerable Content(IEnumerable ids)
+ => ItemsByIds(_publishedSnapshot.Content, ids);
public IEnumerable Content(IEnumerable ids)
- {
- return ids.Select(Content).WhereNotNull();
- }
- public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) =>
- ItemsByXPath(xpath, vars, _publishedSnapshot.Content);
+ => ids.Select(Content).WhereNotNull();
- public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) =>
- ItemsByXPath(xpath, vars, _publishedSnapshot.Content);
+ public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars)
+ => ItemsByXPath(xpath, vars, _publishedSnapshot.Content);
- public IEnumerable ContentAtRoot() => ItemsAtRoot(_publishedSnapshot.Content);
+ public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars)
+ => ItemsByXPath(xpath, vars, _publishedSnapshot.Content);
+
+ public IEnumerable ContentAtRoot()
+ => ItemsAtRoot(_publishedSnapshot.Content);
#endregion
#region Media
- public IPublishedContent Media(int id) => ItemById(id, _publishedSnapshot.Media);
+ public IPublishedContent Media(int id)
+ => ItemById(id, _publishedSnapshot.Media);
- public IPublishedContent Media(Guid id) => ItemById(id, _publishedSnapshot.Media);
+ public IPublishedContent Media(Guid id)
+ => ItemById(id, _publishedSnapshot.Media);
public IPublishedContent Media(Udi id)
- {
- if (!(id is GuidUdi udi)) return null;
- return ItemById(udi.Guid, _publishedSnapshot.Media);
- }
+ => id is GuidUdi udi ? ItemById(udi.Guid, _publishedSnapshot.Media) : null;
public IPublishedContent Media(object id)
{
if (ConvertIdObjectToInt(id, out var intId))
+ {
return Media(intId);
+ }
+
if (ConvertIdObjectToGuid(id, out var guidId))
+ {
return Media(guidId);
+ }
+
if (ConvertIdObjectToUdi(id, out var udiId))
+ {
return Media(udiId);
+ }
+
return null;
}
- public IEnumerable Media(IEnumerable ids) => ItemsByIds(_publishedSnapshot.Media, ids);
+ public IEnumerable Media(IEnumerable ids)
+ => ItemsByIds(_publishedSnapshot.Media, ids);
+
public IEnumerable Media(IEnumerable