diff --git a/.github/workflows/codeql-config.yml b/.github/config/codeql-config.yml
similarity index 100%
rename from .github/workflows/codeql-config.yml
rename to .github/config/codeql-config.yml
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 3d08bf3a4b27..ee912262d7fb 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -56,7 +56,7 @@ jobs:
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
with:
- config-file: ./.github/workflows/codeql-config.yml
+ config-file: ./.github/config/codeql-config.yml
# This job is to prevent the workflow status from showing as failed when all other jobs are skipped - See https://github.community/t/workflow-is-failing-if-no-job-can-be-ran-due-to-condition/16873
always_job:
diff --git a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets
index d271653e4448..f9ee41e13988 100644
--- a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets
+++ b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets
@@ -6,6 +6,16 @@
umbraco
+
+ $(DefaultItemExcludes);App_Plugins\**;
+
+ $(DefaultItemExcludes);umbraco\Data\**;
+ $(DefaultItemExcludes);umbraco\Logs\**;
+ $(DefaultItemExcludes);umbraco\mediacache\**;
+
+ $(DefaultItemExcludes);wwwroot\media\**;
+
+
@@ -21,7 +31,42 @@
SourceFiles="@(ContentWwwrootFiles)"
DestinationFiles="@(ContentWwwrootFiles->'$(MSBuildProjectDirectory)\wwwroot\$(UmbracoWwwrootName)\%(RecursiveDir)%(Filename)%(Extension)')"
SkipUnchangedFiles="true" />
+
+
+
+
+ <_AppPluginsFiles Include="App_Plugins\**" />
+
+
+
+
+
+
+
+ <_UmbracoFolderFiles Include="umbraco\config\**" />
+ <_UmbracoFolderFiles Include="umbraco\PartialViewMacros\**" />
+ <_UmbracoFolderFiles Include="umbraco\UmbracoBackOffice\**" />
+ <_UmbracoFolderFiles Include="umbraco\UmbracoInstall\**" />
+ <_UmbracoFolderFiles Include="umbraco\UmbracoWebsite\**" />
+ <_UmbracoFolderFiles Include="umbraco\UmbracoWebsite\**" />
+ <_UmbracoFolderFiles Include="umbraco\Licenses\**" />
+
+
+ <_UmbracoFolderFiles Include="umbraco\Deploy\**" />
+
+
+
diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index 988265eb5d63..582fadfc49c4 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -226,14 +226,14 @@ stages:
inputs:
command: ci
workingDir: 'tests\Umbraco.Tests.AcceptanceTest'
- - task: Npm@1
+ - task: PowerShell@2
displayName: Run Cypress (Desktop)
condition: always()
continueOnError: true
inputs:
- workingDir: tests\Umbraco.Tests.AcceptanceTest
- command: 'custom'
- customCommand: 'run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"'
+ targetType: inline
+ workingDirectory: tests\Umbraco.Tests.AcceptanceTest
+ script: 'npm run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"'
- task: PublishTestResults@2
condition: always()
@@ -329,14 +329,14 @@ stages:
inputs:
command: ci
workingDir: 'tests/Umbraco.Tests.AcceptanceTest'
- - task: Npm@1
+ - task: Bash@3
displayName: Run Cypress (Desktop)
condition: always()
continueOnError: true
inputs:
- workingDir: tests/Umbraco.Tests.AcceptanceTest
- command: 'custom'
- customCommand: 'run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"'
+ targetType: inline
+ workingDirectory: tests/Umbraco.Tests.AcceptanceTest
+ script: 'npm run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"'
- task: PublishTestResults@2
condition: always()
inputs:
diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/build/templates/UmbracoProject/UmbracoProject.csproj
index 99e72bae0f5e..1b3290927d36 100644
--- a/build/templates/UmbracoProject/UmbracoProject.csproj
+++ b/build/templates/UmbracoProject/UmbracoProject.csproj
@@ -3,9 +3,6 @@
net5.0
Umbraco.Cms.Web.UI
- $(DefaultItemExcludes);App_Plugins/**;
- $(DefaultItemExcludes);umbraco/**;
- $(DefaultItemExcludes);wwwroot/media/**;
@@ -26,16 +23,6 @@
-
-
-
-
-
-
-
-
-
-
true
diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs
index 1b7c6d46fc37..4af6685d8a7e 100644
--- a/src/JsonSchema/AppSettings.cs
+++ b/src/JsonSchema/AppSettings.cs
@@ -1,82 +1,120 @@
-using Umbraco.Cms.Core.Configuration.Models;
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Forms.Core.Configuration;
using SecuritySettings = Umbraco.Cms.Core.Configuration.Models.SecuritySettings;
namespace JsonSchema
{
- public class AppSettings
+ internal class AppSettings
{
+ ///
+ /// Gets or sets the Umbraco
+ ///
public UmbracoDefinition Umbraco { get; set; }
///
- /// Configuration of Umbraco CMS and packages
+ /// Configuration of Umbraco CMS and packages
///
- public class UmbracoDefinition
+ internal class UmbracoDefinition
{
+ // ReSharper disable once InconsistentNaming
public CmsDefinition CMS { get; set; }
+
public FormsDefinition Forms { get; set; }
+
public DeployDefinition Deploy { get; set; }
///
- /// Configurations for the Umbraco CMS
+ /// Configurations for the Umbraco CMS
///
public class CmsDefinition
{
public ActiveDirectorySettings ActiveDirectory { get; set; }
+
public ContentSettings Content { get; set; }
+
public ExceptionFilterSettings ExceptionFilter { get; set; }
+
public ModelsBuilderSettings ModelsBuilder { get; set; }
+
public GlobalSettings Global { get; set; }
+
public HealthChecksSettings HealthChecks { get; set; }
+
public HostingSettings Hosting { get; set; }
+
public ImagingSettings Imaging { get; set; }
+
public IndexCreatorSettings Examine { get; set; }
+
public KeepAliveSettings KeepAlive { get; set; }
+
public LoggingSettings Logging { get; set; }
+
public MemberPasswordConfigurationSettings MemberPassword { get; set; }
+
public NuCacheSettings NuCache { get; set; }
+
public RequestHandlerSettings RequestHandler { get; set; }
+
public RuntimeSettings Runtime { get; set; }
+
public SecuritySettings Security { get; set; }
+
public TourSettings Tours { get; set; }
+
public TypeFinderSettings TypeFinder { get; set; }
+
public UserPasswordConfigurationSettings UserPassword { get; set; }
+
public WebRoutingSettings WebRouting { get; set; }
+
public UmbracoPluginSettings Plugins { get; set; }
+
public UnattendedSettings Unattended { get; set; }
+
public RichTextEditorSettings RichTextEditor { get; set; }
+
public RuntimeMinificationSettings RuntimeMinification { get; set; }
+
public BasicAuthSettings BasicAuth { get; set; }
+
public PackageMigrationSettings PackageMigration { get; set; }
}
///
- /// Configurations for the Umbraco Forms package to Umbraco CMS
+ /// Configurations for the Umbraco Forms package to Umbraco CMS
///
public class FormsDefinition
{
public FormDesignSettings FormDesign { get; set; }
+
public PackageOptionSettings Options { get; set; }
+
public Umbraco.Forms.Core.Configuration.SecuritySettings Security { get; set; }
+
public FieldTypesDefinition FieldTypes { get; set; }
///
- /// Configurations for the Umbraco Forms Field Types
+ /// Configurations for the Umbraco Forms Field Types
///
public class FieldTypesDefinition
{
public DatePickerSettings DatePicker { get; set; }
+
public Recaptcha2Settings Recaptcha2 { get; set; }
+
public Recaptcha3Settings Recaptcha3 { get; set; }
}
}
///
- /// Configurations for the Umbraco Deploy package to Umbraco CMS
+ /// Configurations for the Umbraco Deploy package to Umbraco CMS
///
public class DeployDefinition
{
-
}
}
}
diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj
index 3b64612b62ce..942bdd970556 100644
--- a/src/JsonSchema/JsonSchema.csproj
+++ b/src/JsonSchema/JsonSchema.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/src/JsonSchema/NamespacePrefixedSchemaNameGenerator.cs b/src/JsonSchema/NamespacePrefixedSchemaNameGenerator.cs
index 77b7b8c47445..54ce0fdedfdf 100644
--- a/src/JsonSchema/NamespacePrefixedSchemaNameGenerator.cs
+++ b/src/JsonSchema/NamespacePrefixedSchemaNameGenerator.cs
@@ -1,13 +1,13 @@
-using System;
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System;
using NJsonSchema.Generation;
namespace JsonSchema
{
- public class NamespacePrefixedSchemaNameGenerator : DefaultSchemaNameGenerator
+ internal class NamespacePrefixedSchemaNameGenerator : DefaultSchemaNameGenerator
{
- public override string Generate(Type type)
- {
- return type.Namespace.Replace(".", String.Empty) + base.Generate(type);
- }
+ public override string Generate(Type type) => type.Namespace.Replace(".", string.Empty) + base.Generate(type);
}
}
diff --git a/src/JsonSchema/Options.cs b/src/JsonSchema/Options.cs
new file mode 100644
index 000000000000..9930210cd884
--- /dev/null
+++ b/src/JsonSchema/Options.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using CommandLine;
+
+namespace JsonSchema
+{
+ internal class Options
+ {
+ [Option('o', "outputFile", Required = false, HelpText = "Set path of the output file.", Default = "../../../../Umbraco.Web.UI/umbraco/config/appsettings-schema.json")]
+ public string OutputFile { get; set; }
+ }
+}
diff --git a/src/JsonSchema/Program.cs b/src/JsonSchema/Program.cs
index cd0909302060..8b02068c46ed 100644
--- a/src/JsonSchema/Program.cs
+++ b/src/JsonSchema/Program.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
using System;
using System.IO;
using System.Threading.Tasks;
@@ -5,14 +8,8 @@
namespace JsonSchema
{
- class Program
+ internal class Program
{
- private class Options
- {
- [Option('o', "outputFile", Required = false, HelpText = "Set path of the output file.", Default = "../../../../Umbraco.Web.UI/umbraco/config/appsettings-schema.json")]
- public string OutputFile { get; set; }
- }
-
public static async Task Main(string[] args)
{
try
@@ -25,7 +22,6 @@ await Parser.Default.ParseArguments(args)
Console.WriteLine(e);
throw;
}
-
}
private static async Task Execute(Options options)
@@ -34,7 +30,7 @@ private static async Task Execute(Options options)
var schema = await generator.Generate();
var path = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, options.OutputFile));
- Console.WriteLine("Path to use {0}",path);
+ Console.WriteLine("Path to use {0}", path);
Directory.CreateDirectory(Path.GetDirectoryName(path));
Console.WriteLine("Ensured directory exists");
await File.WriteAllTextAsync(path, schema);
diff --git a/src/JsonSchema/UmbracoJsonSchemaGenerator.cs b/src/JsonSchema/UmbracoJsonSchemaGenerator.cs
index b6e516d0c5ec..e06189d3b432 100644
--- a/src/JsonSchema/UmbracoJsonSchemaGenerator.cs
+++ b/src/JsonSchema/UmbracoJsonSchemaGenerator.cs
@@ -1,4 +1,7 @@
-using System.Net.Http;
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -7,28 +10,26 @@
namespace JsonSchema
{
///
- /// Generator of the JsonSchema for AppSettings.json including A specific Umbraco version.
+ /// Generator of the JsonSchema for AppSettings.json including A specific Umbraco version.
///
public class UmbracoJsonSchemaGenerator
{
+ private static readonly HttpClient s_client = new ();
private readonly JsonSchemaGenerator _innerGenerator;
- private static readonly HttpClient s_client = new HttpClient();
///
- /// Creates a new instance of .
+ /// Initializes a new instance of the class.
///
- /// The prefix to use for definitions generated.
public UmbracoJsonSchemaGenerator()
=> _innerGenerator = new JsonSchemaGenerator(new UmbracoJsonSchemaGeneratorSettings());
-
///
- /// Generates a json representing the JsonSchema for AppSettings.json including A specific Umbraco version..
+ /// Generates a json representing the JsonSchema for AppSettings.json including A specific Umbraco version..
///
public async Task Generate()
{
- var umbracoSchema = GenerateUmbracoSchema();
- var officialSchema = await GetOfficialAppSettingsSchema();
+ JObject umbracoSchema = GenerateUmbracoSchema();
+ JObject officialSchema = await GetOfficialAppSettingsSchema();
officialSchema.Merge(umbracoSchema);
@@ -37,19 +38,17 @@ public async Task Generate()
private async Task GetOfficialAppSettingsSchema()
{
+ HttpResponseMessage response = await s_client.GetAsync("https://json.schemastore.org/appsettings.json")
+ .ConfigureAwait(false);
- var response = await s_client.GetAsync("https://json.schemastore.org/appsettings.json");
-
-
- var result = await response.Content.ReadAsStringAsync();
+ var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject(result);
-
}
private JObject GenerateUmbracoSchema()
{
- var schema = _innerGenerator.Generate(typeof(AppSettings));
+ NJsonSchema.JsonSchema schema = _innerGenerator.Generate(typeof(AppSettings));
return JsonConvert.DeserializeObject(schema.ToJson());
}
diff --git a/src/JsonSchema/UmbracoJsonSchemaGeneratorSettings.cs b/src/JsonSchema/UmbracoJsonSchemaGeneratorSettings.cs
index 26af0faae2db..46625aeb2cdd 100644
--- a/src/JsonSchema/UmbracoJsonSchemaGeneratorSettings.cs
+++ b/src/JsonSchema/UmbracoJsonSchemaGeneratorSettings.cs
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
@@ -12,15 +15,14 @@ namespace JsonSchema
public class UmbracoJsonSchemaGeneratorSettings : JsonSchemaGeneratorSettings
{
///
- /// Creates a new instance of .
+ /// Initializes a new instance of the class.
///
- /// The prefix to use for definitions generated.
public UmbracoJsonSchemaGeneratorSettings()
{
AlwaysAllowAdditionalObjectProperties = true;
SerializerSettings = new JsonSerializerSettings()
{
- ContractResolver = new WritablePropertiesOnlyResolver()
+ ContractResolver = new WritablePropertiesOnlyResolver()
};
DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull;
SchemaNameGenerator = new NamespacePrefixedSchemaNameGenerator();
diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs
index ddd9f96c73a2..670574896ee1 100644
--- a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs
+++ b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -18,6 +18,8 @@ public class FastDictionaryAppCache : IAppCache
///
private readonly ConcurrentDictionary> _items = new ConcurrentDictionary>();
+ public IEnumerable Keys => _items.Keys;
+
public int Count => _items.Count;
///
diff --git a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs
index 4fb335be847d..21e5cc03ed86 100644
--- a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs
+++ b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs
@@ -1,6 +1,7 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Extensions;
+using System.Text;
namespace Umbraco.Cms.Core.Exceptions
{
@@ -86,18 +87,28 @@ public InvalidCompositionException(string contentTypeAlias, string addedComposit
private static string FormatMessage(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases)
{
- // TODO Add property group aliases to message
- return addedCompositionAlias.IsNullOrWhiteSpace()
- ? string.Format(
- "ContentType with alias '{0}' has an invalid composition " +
- "and there was a conflict on the following PropertyTypes: '{1}'. " +
- "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
- contentTypeAlias, string.Join(", ", propertyTypeAliases))
- : string.Format(
- "ContentType with alias '{0}' was added as a Composition to ContentType with alias '{1}', " +
- "but there was a conflict on the following PropertyTypes: '{2}'. " +
- "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
- addedCompositionAlias, contentTypeAlias, string.Join(", ", propertyTypeAliases));
+ var sb = new StringBuilder();
+
+ if (addedCompositionAlias.IsNullOrWhiteSpace())
+ {
+ sb.AppendFormat("Content type with alias '{0}' has an invalid composition.", contentTypeAlias);
+ }
+ else
+ {
+ sb.AppendFormat("Content type with alias '{0}' was added as a composition to content type with alias '{1}', but there was a conflict.", addedCompositionAlias, contentTypeAlias);
+ }
+
+ if (propertyTypeAliases.Length > 0)
+ {
+ sb.AppendFormat(" Property types must have a unique alias across all compositions, these aliases are duplicate: {0}.", string.Join(", ", propertyTypeAliases));
+ }
+
+ if (propertyGroupAliases.Length > 0)
+ {
+ sb.AppendFormat(" Property groups with the same alias must also have the same type across all compositions, these aliases have different types: {0}.", string.Join(", ", propertyGroupAliases));
+ }
+
+ return sb.ToString();
}
///
diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
index 8c4e57244291..197f6f6d63fd 100644
--- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
+++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
@@ -1328,8 +1328,8 @@ private static Dictionary GetAliasesAndNames(IContentTypeService
{"NodeTypeAlias", "NodeTypeAlias"},
{"CreateDate", "CreateDate"},
{"UpdateDate", "UpdateDate"},
- {"CreatorName", "CreatorName"},
- {"WriterName", "WriterName"},
+ {"CreatorId", "CreatorId"},
+ {"WriterId", "WriterId"},
{"Url", "Url"}
};
diff --git a/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs
index b714caf7440d..b4ffc401305d 100644
--- a/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs
+++ b/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Extensions
public static class PublishedModelFactoryExtensions
{
///
- /// Returns true if the current is an implementation of and is enabled
+ /// Returns true if the current is an implementation of and is enabled
///
public static bool IsLiveFactoryEnabled(this IPublishedModelFactory factory)
{
@@ -21,8 +21,8 @@ public static bool IsLiveFactoryEnabled(this IPublishedModelFactory factory)
return liveFactory.Enabled;
}
- // if it's not ILivePublishedModelFactory we can't determine if it's enabled or not so return true
- return true;
+ // if it's not ILivePublishedModelFactory we know we're not using a live factory
+ return false;
}
///
diff --git a/src/Umbraco.Core/PropertyEditors/VoidEditor.cs b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs
index 716e722be14c..8510c982ab03 100644
--- a/src/Umbraco.Core/PropertyEditors/VoidEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs
@@ -1,4 +1,4 @@
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Serialization;
@@ -16,8 +16,6 @@ namespace Umbraco.Cms.Core.PropertyEditors
[HideFromTypeFinder]
public class VoidEditor : DataEditor
{
- private readonly IJsonSerializer _jsonSerializer;
-
///
/// Initializes a new instance of the class.
///
diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs
index 34152ebc0fca..2c79e68e1d8b 100644
--- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs
+++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs
@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PublishedCache.Internal
{
-
+ // TODO: Only used in unit tests, needs to be moved to test project
+ [EditorBrowsable(EditorBrowsableState.Never)]
public sealed class InternalPublishedContent : IPublishedContent
{
public InternalPublishedContent(IPublishedContentType contentType)
diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs
index 2a197affff57..c2f8f274193b 100644
--- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs
+++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs
@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Xml;
namespace Umbraco.Cms.Core.PublishedCache.Internal
{
+ // TODO: Only used in unit tests, needs to be moved to test project
+ [EditorBrowsable(EditorBrowsableState.Never)]
public sealed class InternalPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache
{
private readonly Dictionary _content = new Dictionary();
diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs
index a98eff6484f2..7fe46d8d75ca 100644
--- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs
+++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs
@@ -1,7 +1,10 @@
-using Umbraco.Cms.Core.Models.PublishedContent;
+using System.ComponentModel;
+using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Cms.Core.PublishedCache.Internal
{
+ // TODO: Only used in unit tests, needs to be moved to test project
+ [EditorBrowsable(EditorBrowsableState.Never)]
public class InternalPublishedProperty : IPublishedProperty
{
public IPublishedPropertyType PropertyType { get; set; }
diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs
index c73b6cef7692..1ff8d99139ca 100644
--- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs
+++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs
@@ -1,8 +1,12 @@
using System;
+using System.ComponentModel;
using Umbraco.Cms.Core.Cache;
namespace Umbraco.Cms.Core.PublishedCache.Internal
{
+
+ // TODO: Only used in unit tests, needs to be moved to test project
+ [EditorBrowsable(EditorBrowsableState.Never)]
public sealed class InternalPublishedSnapshot : IPublishedSnapshot
{
public InternalPublishedContentCache InnerContentCache { get; } = new InternalPublishedContentCache();
diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs
index 5dcdc5189f7c..61e2a9c2a9b5 100644
--- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs
+++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs
@@ -1,10 +1,13 @@
using System.Collections.Generic;
+using System.ComponentModel;
using System.Threading.Tasks;
using Umbraco.Cms.Core.Cache;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PublishedCache.Internal
{
+ // TODO: Only used in unit tests, needs to be moved to test project
+ [EditorBrowsable(EditorBrowsableState.Never)]
public class InternalPublishedSnapshotService : IPublishedSnapshotService
{
private InternalPublishedSnapshot _snapshot;
diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
index 16248854e584..ef7a29afaf55 100644
--- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
+++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
@@ -1,116 +1,109 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
+using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
+using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Routing
{
///
- /// Provides urls.
+ /// Provides urls.
///
public class DefaultUrlProvider : IUrlProvider
{
- private readonly RequestHandlerSettings _requestSettings;
+ private readonly ILocalizationService _localizationService;
+ private readonly ILocalizedTextService _localizedTextService;
private readonly ILogger _logger;
+ private readonly RequestHandlerSettings _requestSettings;
private readonly ISiteDomainMapper _siteDomainMapper;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly UriUtility _uriUtility;
- public DefaultUrlProvider(IOptions requestSettings, ILogger logger, ISiteDomainMapper siteDomainMapper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility)
+ [Obsolete("Use ctor with all parameters")]
+ public DefaultUrlProvider(IOptions requestSettings, ILogger logger,
+ ISiteDomainMapper siteDomainMapper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility)
+ : this(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility,
+ StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
+ public DefaultUrlProvider(
+ IOptions requestSettings,
+ ILogger logger,
+ ISiteDomainMapper siteDomainMapper,
+ IUmbracoContextAccessor umbracoContextAccessor,
+ UriUtility uriUtility,
+ ILocalizationService localizationService)
{
_requestSettings = requestSettings.Value;
_logger = logger;
_siteDomainMapper = siteDomainMapper;
- _uriUtility = uriUtility;
_umbracoContextAccessor = umbracoContextAccessor;
+ _uriUtility = uriUtility;
+ _localizationService = localizationService;
}
- #region GetUrl
-
- ///
- public virtual UrlInfo GetUrl(IPublishedContent content, UrlMode mode, string culture, Uri current)
- {
- if (!current.IsAbsoluteUri) throw new ArgumentException("Current URL must be absolute.", nameof(current));
- var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
- // will not use cache if previewing
- var route = umbracoContext.Content.GetRouteById(content.Id, culture);
-
- return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture);
- }
-
- internal UrlInfo GetUrlFromRoute(string route, IUmbracoContext umbracoContext, int id, Uri current, UrlMode mode, string culture)
- {
- if (string.IsNullOrWhiteSpace(route))
- {
- _logger.LogDebug("Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.", id);
- return null;
- }
-
- // extract domainUri and path
- // route is / or /
- var pos = route.IndexOf('/');
- var path = pos == 0 ? route : route.Substring(pos);
- var domainUri = pos == 0
- ? null
- : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture);
-
- // assemble the URL from domainUri (maybe null) and path
- var url = AssembleUrl(domainUri, path, current, mode).ToString();
-
- return UrlInfo.Url(url, culture);
- }
-
- #endregion
-
#region GetOtherUrls
///
- /// Gets the other URLs of a published content.
+ /// Gets the other URLs of a published content.
///
/// The Umbraco context.
/// The published content id.
/// The current absolute URL.
/// The other URLs for the published content.
///
- /// Other URLs are those that GetUrl would not return in the current context, but would be valid
- /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...).
+ ///
+ /// Other URLs are those that GetUrl would not return in the current context, but would be valid
+ /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...).
+ ///
///
public virtual IEnumerable GetOtherUrls(int id, Uri current)
{
- var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
- var node = umbracoContext.Content.GetById(id);
+ IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
+ IPublishedContent node = umbracoContext.Content.GetById(id);
if (node == null)
{
yield break;
}
// look for domains, walking up the tree
- var n = node;
- var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false);
+ IPublishedContent n = node;
+ IEnumerable domainUris =
+ DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id,
+ current, false);
while (domainUris == null && n != null) // n is null at root
{
n = n.Parent; // move to parent node
- domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, excludeDefault: true);
+ domainUris = n == null
+ ? null
+ : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id,
+ current);
}
// no domains = exit
- if (domainUris ==null)
+ if (domainUris == null)
{
yield break;
}
- foreach (var d in domainUris)
+ foreach (DomainAndUri d in domainUris)
{
var culture = d?.Culture;
// although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok
var route = umbracoContext.Content.GetRouteById(id, culture);
- if (route == null) continue;
+ if (route == null)
+ {
+ continue;
+ }
// need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned)
var pos = route.IndexOf('/');
@@ -124,9 +117,57 @@ public virtual IEnumerable GetOtherUrls(int id, Uri current)
#endregion
+ #region GetUrl
+
+ ///
+ public virtual UrlInfo GetUrl(IPublishedContent content, UrlMode mode, string culture, Uri current)
+ {
+ if (!current.IsAbsoluteUri)
+ {
+ throw new ArgumentException("Current URL must be absolute.", nameof(current));
+ }
+
+ IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
+ // will not use cache if previewing
+ var route = umbracoContext.Content.GetRouteById(content.Id, culture);
+
+ return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture);
+ }
+
+ internal UrlInfo GetUrlFromRoute(string route, IUmbracoContext umbracoContext, int id, Uri current,
+ UrlMode mode, string culture)
+ {
+ if (string.IsNullOrWhiteSpace(route))
+ {
+ _logger.LogDebug(
+ "Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.",
+ id);
+ return null;
+ }
+
+ // extract domainUri and path
+ // route is / or /
+ var pos = route.IndexOf('/');
+ var path = pos == 0 ? route : route.Substring(pos);
+ var domainUri = pos == 0
+ ? null
+ : 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 == defaultCulture || culture is null)
+ {
+ var url = AssembleUrl(domainUri, path, current, mode).ToString();
+ return UrlInfo.Url(url, culture);
+ }
+
+ return null;
+ }
+
+ #endregion
+
#region Utilities
- Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode)
+ private Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode)
{
Uri uri;
@@ -135,7 +176,9 @@ Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode)
if (domainUri == null) // no domain was found
{
if (current == null)
+ {
mode = UrlMode.Relative; // best we can do
+ }
switch (mode)
{
@@ -155,10 +198,15 @@ Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode)
if (mode == UrlMode.Auto)
{
//this check is a little tricky, we can't just compare domains
- if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority))
+ if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) ==
+ current.GetLeftPart(UriPartial.Authority))
+ {
mode = UrlMode.Relative;
+ }
else
+ {
mode = UrlMode.Absolute;
+ }
}
switch (mode)
@@ -179,9 +227,9 @@ Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode)
return _uriUtility.UriFromUmbraco(uri, _requestSettings);
}
- string CombinePaths(string path1, string path2)
+ private string CombinePaths(string path1, string path2)
{
- string path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2;
+ var path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2;
return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash);
}
diff --git a/src/Umbraco.Core/Routing/SiteDomainMapper.cs b/src/Umbraco.Core/Routing/SiteDomainMapper.cs
index d877385ef107..455889d015eb 100644
--- a/src/Umbraco.Core/Routing/SiteDomainMapper.cs
+++ b/src/Umbraco.Core/Routing/SiteDomainMapper.cs
@@ -4,18 +4,36 @@
using System.Text.RegularExpressions;
using System.Threading;
using Umbraco.Extensions;
-using System.ComponentModel;
namespace Umbraco.Cms.Core.Routing
{
///
- /// Provides utilities to handle site domains.
+ /// Provides utilities to handle site domains.
///
public class SiteDomainMapper : ISiteDomainMapper, IDisposable
{
+ public void Dispose() =>
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(true);
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ // This is pretty nasty disposing a static on an instance but it's because this whole class
+ // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics.
+ _configLock.Dispose();
+ }
+
+ _disposedValue = true;
+ }
+ }
+
#region Configure
- private readonly ReaderWriterLockSlim _configLock = new ReaderWriterLockSlim();
+ private readonly ReaderWriterLockSlim _configLock = new();
private Dictionary> _qualifiedSites;
private bool _disposedValue;
@@ -25,10 +43,12 @@ public class SiteDomainMapper : ISiteDomainMapper, IDisposable
// these are for validation
//private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$";
private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$";
- private static readonly Regex s_domainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled);
+
+ private static readonly Regex s_domainValidation =
+ new(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled);
///
- /// Clears the entire configuration.
+ /// Clears the entire configuration.
///
public void Clear()
{
@@ -49,20 +69,21 @@ public void Clear()
}
}
- private IEnumerable ValidateDomains(IEnumerable domains)
- {
+ private IEnumerable ValidateDomains(IEnumerable domains) =>
// must use authority format w/optional scheme and port, but no path
// any domain should appear only once
- return domains.Select(domain =>
+ domains.Select(domain =>
+ {
+ if (!s_domainValidation.IsMatch(domain))
{
- if (!s_domainValidation.IsMatch(domain))
- throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\".");
- return domain;
- });
- }
+ throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\".");
+ }
+
+ return domain;
+ });
///
- /// Adds a site.
+ /// Adds a site.
///
/// A key uniquely identifying the site.
/// The site domains.
@@ -87,7 +108,7 @@ public void AddSite(string key, IEnumerable domains)
}
///
- /// Adds a site.
+ /// Adds a site.
///
/// A key uniquely identifying the site.
/// The site domains.
@@ -112,7 +133,7 @@ public void AddSite(string key, params string[] domains)
}
///
- /// Removes a site.
+ /// Removes a site.
///
/// A key uniquely identifying the site.
internal void RemoveSite(string key)
@@ -122,11 +143,15 @@ internal void RemoveSite(string key)
_configLock.EnterWriteLock();
if (Sites == null || !Sites.ContainsKey(key))
+ {
return;
+ }
Sites.Remove(key);
if (Sites.Count == 0)
+ {
Sites = null;
+ }
if (Bindings != null && Bindings.ContainsKey(key))
{
@@ -134,11 +159,16 @@ internal void RemoveSite(string key)
{
Bindings[b].Remove(key);
if (Bindings[b].Count == 0)
+ {
Bindings.Remove(b);
+ }
}
+
Bindings.Remove(key);
if (Bindings.Count > 0)
+ {
Bindings = null;
+ }
}
_qualifiedSites = null;
@@ -153,12 +183,12 @@ internal void RemoveSite(string key)
}
///
- /// Binds some sites.
+ /// Binds some sites.
///
/// The keys uniquely identifying the sites to bind.
///
- /// At the moment there is no public way to unbind sites. Clear and reconfigure.
- /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3.
+ /// At the moment there is no public way to unbind sites. Clear and reconfigure.
+ /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3.
///
public void BindSites(params string[] keys)
{
@@ -167,7 +197,9 @@ public void BindSites(params string[] keys)
_configLock.EnterWriteLock();
foreach (var key in keys.Where(key => !Sites.ContainsKey(key)))
+ {
throw new ArgumentException($"Not an existing site key: {key}.", nameof(keys));
+ }
Bindings = Bindings ?? new Dictionary>();
@@ -180,9 +212,12 @@ public void BindSites(params string[] keys)
foreach (var key in allkeys)
{
if (!Bindings.ContainsKey(key))
+ {
Bindings[key] = new List();
+ }
+
var xkey = key;
- var addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]);
+ IEnumerable addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]);
Bindings[key].AddRange(addKeys);
}
}
@@ -200,16 +235,18 @@ public void BindSites(params string[] keys)
#region Map domains
///
- public virtual DomainAndUri MapDomain(IReadOnlyCollection domainAndUris, Uri current, string culture, string defaultCulture)
+ public virtual DomainAndUri MapDomain(IReadOnlyCollection domainAndUris, Uri current,
+ string culture, string defaultCulture)
{
var currentAuthority = current.GetLeftPart(UriPartial.Authority);
- var qualifiedSites = GetQualifiedSites(current);
+ Dictionary qualifiedSites = GetQualifiedSites(current);
return MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture);
}
///
- public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, bool excludeDefault, string culture, string defaultCulture)
+ public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris,
+ Uri current, bool excludeDefault, string culture, string defaultCulture)
{
// TODO: ignoring cultures entirely?
@@ -221,15 +258,18 @@ public virtual IEnumerable MapDomains(IReadOnlyCollection qualifiedSites = GetQualifiedSitesInsideLock(current);
if (excludeDefault)
{
// exclude the current one (avoid producing the absolute equivalent of what GetUrl returns)
- var hintWithSlash = current.EndPathWithSlash();
- var hinted = domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash));
+ Uri hintWithSlash = current.EndPathWithSlash();
+ DomainAndUri hinted =
+ domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash));
if (hinted != null)
+ {
ret = ret.Where(d => d != hinted);
+ }
// exclude the default one (avoid producing a possible duplicate of what GetUrl returns)
// only if the default one cannot be the current one ie if hinted is not null
@@ -237,17 +277,21 @@ public virtual IEnumerable MapDomains(IReadOnlyCollection d != mainDomain);
}
}
// we do our best, but can't do the impossible
if (qualifiedSites == null)
+ {
return ret;
+ }
// find a site that contains the current authority
- var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
+ KeyValuePair currentSite =
+ qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
// if current belongs to a site, pick every element from domainAndUris that also belong
// to that site -- or to any site bound to that site
@@ -257,7 +301,8 @@ public virtual IEnumerable MapDomains(IReadOnlyCollection Bindings[currentSite.Key].Contains(site.Key));
+ IEnumerable> boundSites =
+ qualifiedSites.Where(site => Bindings[currentSite.Key].Contains(site.Key));
candidateSites = candidateSites.Union(boundSites).ToArray();
// .ToArray ensures it is evaluated before the configuration lock is exited
@@ -273,7 +318,9 @@ public virtual IEnumerable MapDomains(IReadOnlyCollection
+ return candidateSites == null
+ ? ret
+ : ret.Where(d =>
{
var authority = d.Uri.GetLeftPart(UriPartial.Authority);
return candidateSites.Any(site => site.Value.Contains(authority));
@@ -301,11 +348,15 @@ private Dictionary GetQualifiedSitesInsideLock(Uri current)
{
// we do our best, but can't do the impossible
if (Sites == null)
+ {
return null;
+ }
// cached?
if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme))
+ {
return _qualifiedSites[current.Scheme];
+ }
_qualifiedSites = _qualifiedSites ?? new Dictionary>();
@@ -314,7 +365,10 @@ private Dictionary GetQualifiedSitesInsideLock(Uri current)
return _qualifiedSites[current.Scheme] = Sites
.ToDictionary(
kvp => kvp.Key,
- kvp => kvp.Value.Select(d => new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme)).GetLeftPart(UriPartial.Authority)).ToArray()
+ kvp => kvp.Value.Select(d =>
+ new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme))
+ .GetLeftPart(UriPartial.Authority))
+ .ToArray()
);
// .ToDictionary will evaluate and create the dictionary immediately
@@ -322,61 +376,54 @@ private Dictionary GetQualifiedSitesInsideLock(Uri current)
// therefore it is safe to return and exit the configuration lock
}
- private DomainAndUri MapDomain(IReadOnlyCollection domainAndUris, Dictionary qualifiedSites, string currentAuthority, string culture, string defaultCulture)
+ private DomainAndUri MapDomain(IReadOnlyCollection domainAndUris,
+ Dictionary qualifiedSites, string currentAuthority, string culture, string defaultCulture)
{
if (domainAndUris == null)
+ {
throw new ArgumentNullException(nameof(domainAndUris));
+ }
+
if (domainAndUris.Count == 0)
+ {
throw new ArgumentException("Cannot be empty.", nameof(domainAndUris));
+ }
- // TODO: how shall we deal with cultures?
-
- // we do our best, but can't do the impossible
- // get the "default" domain ie the first one for the culture, else the first one (exists, length > 0)
if (qualifiedSites == null)
{
return domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture))
- ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture));
+ ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture))
+ ?? (culture is null ? domainAndUris.First() : null);
}
// find a site that contains the current authority
- var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
+ KeyValuePair currentSite =
+ qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
// if current belongs to a site - try to pick the first element
// from domainAndUris that also belongs to that site
- var ret = currentSite.Equals(default(KeyValuePair))
+ DomainAndUri ret = currentSite.Equals(default(KeyValuePair))
? null
- : domainAndUris.FirstOrDefault(d => currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority)));
+ : domainAndUris.FirstOrDefault(d =>
+ currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority)));
// no match means that either current does not belong to a site, or the site it belongs to
- // does not contain any of domainAndUris.
- return ret;
- }
+ // does not contain any of domainAndUris. Yet we have to return something. here, it becomes
+ // a bit arbitrary.
+ // look through sites in order and pick the first domainAndUri that belongs to a site
+ ret = ret ?? qualifiedSites
+ .Where(site => site.Key != currentSite.Key)
+ .Select(site => domainAndUris.FirstOrDefault(domainAndUri =>
+ site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority))))
+ .FirstOrDefault(domainAndUri => domainAndUri != null);
- #endregion
+ // random, really
+ ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First();
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposedValue)
- {
- if (disposing)
- {
- // This is pretty nasty disposing a static on an instance but it's because this whole class
- // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics.
- _configLock.Dispose();
- }
-
- _disposedValue = true;
- }
- }
-
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
- Dispose(disposing: true);
+ return ret;
}
-
+ #endregion
}
}
diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
index c3e2bdee77db..3ba254112b22 100644
--- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
+++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
@@ -15,11 +15,11 @@ namespace Umbraco.Extensions
public static class UrlProviderExtensions
{
///
- /// Gets the URLs of the content item.
+ /// Gets the URLs of the content item.
///
///
- /// Use when displaying URLs. If errors occur when generating the URLs, they will show in the list.
- /// Contains all the URLs that we can figure out (based upon domains, etc).
+ /// Use when displaying URLs. If errors occur when generating the URLs, they will show in the list.
+ /// Contains all the URLs that we can figure out (based upon domains, etc).
///
public static async Task> GetContentUrlsAsync(
this IContent content,
@@ -33,16 +33,55 @@ public static async Task> GetContentUrlsAsync(
UriUtility uriUtility,
IPublishedUrlProvider publishedUrlProvider)
{
- if (content == null) throw new ArgumentNullException(nameof(content));
- if (publishedRouter == null) throw new ArgumentNullException(nameof(publishedRouter));
- if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext));
- if (localizationService == null) throw new ArgumentNullException(nameof(localizationService));
- if (textService == null) throw new ArgumentNullException(nameof(textService));
- if (contentService == null) throw new ArgumentNullException(nameof(contentService));
- if (logger == null) throw new ArgumentNullException(nameof(logger));
- if (publishedUrlProvider == null) throw new ArgumentNullException(nameof(publishedUrlProvider));
- if (uriUtility == null) throw new ArgumentNullException(nameof(uriUtility));
- if (variationContextAccessor == null) throw new ArgumentNullException(nameof(variationContextAccessor));
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+
+ if (publishedRouter == null)
+ {
+ throw new ArgumentNullException(nameof(publishedRouter));
+ }
+
+ if (umbracoContext == null)
+ {
+ throw new ArgumentNullException(nameof(umbracoContext));
+ }
+
+ if (localizationService == null)
+ {
+ throw new ArgumentNullException(nameof(localizationService));
+ }
+
+ if (textService == null)
+ {
+ throw new ArgumentNullException(nameof(textService));
+ }
+
+ if (contentService == null)
+ {
+ throw new ArgumentNullException(nameof(contentService));
+ }
+
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
+
+ if (publishedUrlProvider == null)
+ {
+ throw new ArgumentNullException(nameof(publishedUrlProvider));
+ }
+
+ if (uriUtility == null)
+ {
+ throw new ArgumentNullException(nameof(uriUtility));
+ }
+
+ if (variationContextAccessor == null)
+ {
+ throw new ArgumentNullException(nameof(variationContextAccessor));
+ }
var result = new List();
@@ -68,7 +107,9 @@ public static async Task> GetContentUrlsAsync(
// get all URLs for all cultures
// in a HashSet, so de-duplicates too
- foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider))
+ foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter,
+ umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility,
+ publishedUrlProvider))
{
urls.Add(cultureUrl);
}
@@ -79,15 +120,25 @@ public static async Task> GetContentUrlsAsync(
// in some cases there will be the same URL for multiple cultures:
// * The entire branch is invariant
// * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed
- foreach (UrlInfo dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture))
+
+ if (urlGroup.Key)
+ {
+ result.AddRange(urlGroup.DistinctBy(x => x.Text.ToUpperInvariant())
+ .OrderBy(x => x.Text).ThenBy(x => x.Culture));
+ }
+ else
{
- result.Add(dUrl);
+ result.AddRange(urlGroup);
}
+
+
+
}
// get the 'other' URLs - ie not what you'd get with GetUrl() but URLs that would route to the document, nevertheless.
// for these 'other' URLs, we don't check whether they are routable, collide, anything - we just report them.
- foreach (var otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture))
+ foreach (UrlInfo otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text)
+ .ThenBy(x => x.Culture))
{
// avoid duplicates
if (urls.Add(otherUrl))
@@ -100,7 +151,7 @@ public static async Task> GetContentUrlsAsync(
}
///
- /// Tries to return a for each culture for the content while detecting collisions/errors
+ /// Tries to return a for each culture for the content while detecting collisions/errors
///
private static async Task> GetContentUrlsByCultureAsync(
IContent content,
@@ -151,7 +202,8 @@ private static async Task> GetContentUrlsByCultureAsync(
// got a URL, deal with collisions, add URL
default:
// detect collisions, etc
- Attempt hasCollision = await DetectCollisionAsync(logger, content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility);
+ Attempt hasCollision = await DetectCollisionAsync(logger, content, url, culture,
+ umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility);
if (hasCollision)
{
result.Add(hasCollision.Result);
@@ -168,7 +220,8 @@ private static async Task> GetContentUrlsByCultureAsync(
return result;
}
- private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService)
+ private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService,
+ ILocalizedTextService textService)
{
// document has a published version yet its URL is "#" => a parent must be
// unpublished, walk up the tree until we find it, and report.
@@ -176,27 +229,31 @@ private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IC
do
{
parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null;
- }
- while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture)));
+ } while (parent != null && parent.Published &&
+ (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture)));
if (parent == null)
{
// oops, internal error
return UrlInfo.Message(textService.Localize("content", "parentNotPublishedAnomaly"), culture);
}
- else if (!parent.Published)
+
+ if (!parent.Published)
{
// totally not published
- return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }), culture);
- }
- else
- {
- // culture not published
- return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] {parent.Name}), culture);
+ return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }),
+ culture);
}
+
+ // culture not published
+ return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] { parent.Name }),
+ culture);
}
- private static async Task> DetectCollisionAsync(ILogger logger, IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility)
+ private static async Task> DetectCollisionAsync(ILogger logger, IContent content, string url,
+ string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter,
+ ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor,
+ UriUtility uriUtility)
{
// test for collisions on the 'main' URL
var uri = new Uri(url.TrimEnd(Constants.CharArrays.ForwardSlash), UriKind.RelativeOrAbsolute);
@@ -207,11 +264,13 @@ private static async Task> DetectCollisionAsync(ILogger logger,
uri = uriUtility.UriToUmbraco(uri);
IPublishedRequestBuilder builder = await publishedRouter.CreateRequestAsync(uri);
- IPublishedRequest pcr = await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound));
+ IPublishedRequest pcr =
+ await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound));
if (!pcr.HasPublishedContent())
{
- var logMsg = nameof(DetectCollisionAsync) + " did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}";
+ var logMsg = nameof(DetectCollisionAsync) +
+ " did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}";
if (pcr.IgnorePublishedContentCollisions)
{
logger.LogDebug(logMsg, url, uri, culture);
@@ -243,14 +302,7 @@ private static async Task> DetectCollisionAsync(ILogger logger,
l.Reverse();
var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")";
- var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture);
- return Attempt.Succeed(urlInfo);
- }
-
- // collisions with a different culture of the same content can never be routed.
- if (!culture.InvariantEquals(pcr.Culture))
- {
- var urlInfo = UrlInfo.Message(textService.Localize("content", "routeErrorCannotRoute"), culture);
+ var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture);
return Attempt.Succeed(urlInfo);
}
diff --git a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs
index a266b52f6b6f..fff22936a02f 100644
--- a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs
+++ b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs
@@ -32,7 +32,13 @@ private static string GetUrlSegmentSource(IContentBase content, string culture)
if (content.HasProperty(Constants.Conventions.Content.UrlName))
source = (content.GetValue(Constants.Conventions.Content.UrlName, culture) ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(source))
- source = content.GetCultureName(culture);
+ {
+ // If the name of a node has been updated, but it has not been published, the url should use the published name, not the current node name
+ // If this node has never been published (GetPublishName is null), use the unpublished name
+ source = (content is IContent document) && document.Edited && document.GetPublishName(culture) != null
+ ? document.GetPublishName(culture)
+ : content.GetCultureName(culture);
+ }
return source;
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
index 277a236e24dc..ea629e5c3afb 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
@@ -219,35 +219,32 @@ protected void DefinePlan()
// so we need to ensure that migrations from 8.15 are included in the next
// v9*.
- Merge()
- // to 8.15.0
- .To("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}")
- .To("{4695D0C9-0729-4976-985B-048D503665D8}")
- .To("{5C424554-A32D-4852-8ED1-A13508187901}")
- .With()
- // to 9.0.0 RC1
- .To("{22D801BA-A1FF-4539-BFCC-2139B55594F8}")
- .To("{50A43237-A6F4-49E2-A7A6-5DAD65C84669}")
- .To("{3D8DADEF-0FDA-4377-A5F0-B52C2110E8F2}")
- .To("{1303BDCF-2295-4645-9526-2F32E8B35ABD}")
- .To("{86AC839A-0D08-4D09-B7B5-027445E255A1}")
- .As("{5060F3D2-88BE-4D30-8755-CF51F28EAD12}");
-
- Merge()
- // to 8.17.0
- .To("{153865E9-7332-4C2A-9F9D-F20AEE078EC7}")
- .With()
- // 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}
- // - 9.0.0 RC1 - Current state: {5060F3D2-88BE-4D30-8755-CF51F28EAD12}
- .To("{622E5172-42E1-4662-AD80-9504AF5A4E53}")
- .To("{10F7BB61-C550-426B-830B-7F954F689CDF}")
- .To("{12DCDE7F-9AB7-4617-804F-AB66BF360980}")
- .As("{5AAE6276-80DB-4ACF-B845-199BC6C37538}");
+ // to 8.15.0
+ To("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}");
+ To("{4695D0C9-0729-4976-985B-048D503665D8}");
+ To("{5C424554-A32D-4852-8ED1-A13508187901}");
+
+ // to 8.17.0
+ To("{153865E9-7332-4C2A-9F9D-F20AEE078EC7}");
+
+ // 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}
+ // - 9.0.0 RC1 - Current state: {5060F3D2-88BE-4D30-8755-CF51F28EAD12}
+ To("{622E5172-42E1-4662-AD80-9504AF5A4E53}");
+ To("{10F7BB61-C550-426B-830B-7F954F689CDF}");
+ To("{5AAE6276-80DB-4ACF-B845-199BC6C37538}");
+
+ // to 9.0.0 RC1
+ To("{22D801BA-A1FF-4539-BFCC-2139B55594F8}");
+ To("{50A43237-A6F4-49E2-A7A6-5DAD65C84669}");
+ To("{3D8DADEF-0FDA-4377-A5F0-B52C2110E8F2}");
+ To("{1303BDCF-2295-4645-9526-2F32E8B35ABD}");
+ To("{5060F3D2-88BE-4D30-8755-CF51F28EAD12}");
+ To("{A2686B49-A082-4B22-97FD-AAB154D46A57}"); // Re-run this migration to make sure it has executed to account for migrations going out of sync between versions.
// TO 9.0.0-rc4
- To("5E02F241-5253-403D-B5D3-7DB00157E20F");
+ To("5E02F241-5253-403D-B5D3-7DB00157E20F"); // Jaddie: This GUID is missing the { }, although this likely can't be changed now as it will break installs going forwards
// TO 9.0.0
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs
index 9a7ecdbf294a..cfa1c4b3cbc0 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs
@@ -122,7 +122,7 @@ private void RecursePropertyValues(IEnumerable blockData, Func _logger;
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
+ private bool _disposedValue;
// default ctor
public PublishedContentTypeCache(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, IPublishedContentTypeFactory publishedContentTypeFactory, ILogger logger)
@@ -259,8 +260,6 @@ private IPublishedContentType CreatePublishedContentType(PublishedItemType itemT
private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias)
{
- if (GetPublishedContentTypeByAlias != null)
- return GetPublishedContentTypeByAlias(alias);
IContentTypeComposition contentType = itemType switch
{
PublishedItemType.Content => _contentTypeService.Get(alias),
@@ -276,8 +275,6 @@ private IPublishedContentType CreatePublishedContentType(PublishedItemType itemT
private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, int id)
{
- if (GetPublishedContentTypeById != null)
- return GetPublishedContentTypeById(id);
IContentTypeComposition contentType = itemType switch
{
PublishedItemType.Content => _contentTypeService.Get(id),
@@ -291,56 +288,6 @@ private IPublishedContentType CreatePublishedContentType(PublishedItemType itemT
return _publishedContentTypeFactory.CreateContentType(contentType);
}
- // for unit tests - changing the callback must reset the cache obviously
- // TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id
- private Func _getPublishedContentTypeByAlias;
- internal Func GetPublishedContentTypeByAlias
- {
- get => _getPublishedContentTypeByAlias;
- set
- {
- try
- {
- _lock.EnterWriteLock();
-
- _typesByAlias.Clear();
- _typesById.Clear();
- _getPublishedContentTypeByAlias = value;
- }
- finally
- {
- if (_lock.IsWriteLockHeld)
- _lock.ExitWriteLock();
- }
- }
- }
-
- // for unit tests - changing the callback must reset the cache obviously
- // TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id
- private Func _getPublishedContentTypeById;
- private bool _disposedValue;
-
- internal Func GetPublishedContentTypeById
- {
- get => _getPublishedContentTypeById;
- set
- {
- try
- {
- _lock.EnterWriteLock();
-
- _typesByAlias.Clear();
- _typesById.Clear();
- _getPublishedContentTypeById = value;
- }
- finally
- {
- if (_lock.IsWriteLockHeld)
- _lock.ExitWriteLock();
- }
- }
- }
-
private static string GetAliasKey(PublishedItemType itemType, string alias)
{
string k;
diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index c1c22d0b89a2..21f95f74d62f 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -134,6 +134,9 @@ protected void ValidateLocked(TItem compositionContentType)
stack.Push(c);
}
+ var duplicatePropertyTypeAliases = new List();
+ var invalidPropertyGroupAliases = new List();
+
foreach (var dependency in dependencies)
{
if (dependency.Id == compositionContentType.Id)
@@ -143,13 +146,14 @@ protected void ValidateLocked(TItem compositionContentType)
if (contentTypeDependency == null)
continue;
- var duplicatePropertyTypeAliases = contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase).ToArray();
- var invalidPropertyGroupAliases = contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias).ToArray();
+ duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase));
+ invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias));
+ }
- if (duplicatePropertyTypeAliases.Length == 0 && invalidPropertyGroupAliases.Length == 0)
- continue;
+ if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0)
- throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
+ {
+ throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
}
}
diff --git a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs
index 875e6d2ffc05..455c31d00fa5 100644
--- a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs
+++ b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs
@@ -13,19 +13,24 @@
using Umbraco.Cms.Core.Xml.XPath;
using Umbraco.Cms.Infrastructure.PublishedCache.Navigable;
using Umbraco.Extensions;
-using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Infrastructure.PublishedCache
{
public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigableData, IDisposable
{
- private readonly ContentStore.Snapshot _snapshot;
- private readonly IAppCache _snapshotCache;
- private readonly IAppCache _elementsCache;
private readonly IDomainCache _domainCache;
+ private readonly IAppCache _elementsCache;
private readonly GlobalSettings _globalSettings;
+ private readonly ContentStore.Snapshot _snapshot;
+ private readonly IAppCache _snapshotCache;
private readonly IVariationContextAccessor _variationContextAccessor;
+ #region IDisposable
+
+ public void Dispose() => _snapshot.Dispose();
+
+ #endregion
+
#region Constructor
// TODO: figure this out
@@ -33,7 +38,9 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab
// it's too late for UmbracoContext which has captured previewDefault and stuff into these ctor vars
// but, no, UmbracoContext returns snapshot.Content which comes from elements SO a resync should create a new cache
- public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache, IAppCache elementsCache, IDomainCache domainCache, IOptions globalSettings, IVariationContextAccessor variationContextAccessor)
+ public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache,
+ IAppCache elementsCache, IDomainCache domainCache, IOptions globalSettings,
+ IVariationContextAccessor variationContextAccessor)
: base(previewDefault)
{
_snapshot = snapshot;
@@ -59,18 +66,23 @@ public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCac
// at the moment we try our best to be backward compatible, but really,
// should get rid of hideTopLevelNode and other oddities entirely, eventually
- public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null)
- {
- return GetByRoute(PreviewDefault, route, hideTopLevelNode, culture);
- }
+ public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null) =>
+ GetByRoute(PreviewDefault, route, hideTopLevelNode, culture);
- public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string culture = null)
+ public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null,
+ string culture = null)
{
- if (route == null) throw new ArgumentNullException(nameof(route));
+ if (route == null)
+ {
+ throw new ArgumentNullException(nameof(route));
+ }
- var cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing ? _elementsCache : _snapshotCache;
+
+ IAppCache cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing
+ ? _elementsCache
+ : _snapshotCache;
var key = CacheKeys.ContentCacheContentByRoute(route, preview, culture);
- return cache.GetCacheItem(key, () => GetByRouteInternal(preview, route, hideTopLevelNode, culture));
+ return cache.GetCacheItem(key, () => GetByRouteInternal(preview, route, hideTopLevelNode, culture));
}
private IPublishedContent GetByRouteInternal(bool preview, string route, bool? hideTopLevelNode, string culture)
@@ -108,8 +120,10 @@ private IPublishedContent GetByRouteInternal(bool preview, string route, bool? h
// hideTopLevelNode = support legacy stuff, look for /*/path/to/node
// else normal, look for /path/to/node
content = hideTopLevelNode.Value
- ? GetAtRoot(preview).SelectMany(x => x.Children(_variationContextAccessor, culture)).FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0])
- : GetAtRoot(preview).FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]);
+ ? GetAtRoot(preview).SelectMany(x => x.Children(_variationContextAccessor, culture))
+ .FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0])
+ : GetAtRoot(preview)
+ .FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]);
content = FollowRoute(content, parts, 1, culture);
}
@@ -118,59 +132,72 @@ private IPublishedContent GetByRouteInternal(bool preview, string route, bool? h
// have to look for /foo (see note in ApplyHideTopLevelNodeFromPath).
if (content == null && hideTopLevelNode.Value && parts.Length == 1)
{
- content = GetAtRoot(preview).FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]);
+ content = GetAtRoot(preview)
+ .FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]);
}
return content;
}
- public string GetRouteById(int contentId, string culture = null)
- {
- return GetRouteById(PreviewDefault, contentId, culture);
- }
+ public string GetRouteById(int contentId, string culture = null) =>
+ GetRouteById(PreviewDefault, contentId, culture);
public string GetRouteById(bool preview, int contentId, string culture = null)
{
- var cache = (preview == false || PublishedSnapshotService.FullCacheWhenPreviewing) ? _elementsCache : _snapshotCache;
+ IAppCache cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing
+ ? _elementsCache
+ : _snapshotCache;
var key = CacheKeys.ContentCacheRouteByContent(contentId, preview, culture);
- return cache.GetCacheItem(key, () => GetRouteByIdInternal(preview, contentId, null, culture));
+ return cache.GetCacheItem(key, () => GetRouteByIdInternal(preview, contentId, null, culture));
}
private string GetRouteByIdInternal(bool preview, int contentId, bool? hideTopLevelNode, string culture)
{
- var node = GetById(preview, contentId);
+ IPublishedContent node = GetById(preview, contentId);
if (node == null)
+ {
return null;
+ }
hideTopLevelNode = hideTopLevelNode ?? HideTopLevelNodeFromPath; // default = settings
// walk up from that node until we hit a node with a domain,
// or we reach the content root, collecting URLs in the way
var pathParts = new List();
- var n = node;
+ IPublishedContent n = node;
var urlSegment = n.UrlSegment(_variationContextAccessor, culture);
- var hasDomains = _domainCache.HasAssigned(n.Id);
+ var hasDomains = _domainCache.GetAssignedWithCulture(culture, n.Id);
while (hasDomains == false && n != null) // n is null at root
{
// no segment indicates this is not published when this is a variant
- if (urlSegment.IsNullOrWhiteSpace()) return null;
+ if (urlSegment.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
pathParts.Add(urlSegment);
// move to parent node
n = n.Parent;
if (n != null)
+ {
urlSegment = n.UrlSegment(_variationContextAccessor, culture);
+ }
- hasDomains = n != null && _domainCache.HasAssigned(n.Id);
+ hasDomains = n != null && _domainCache.GetAssignedWithCulture(culture, n.Id);
}
// at this point this will be the urlSegment of the root, no segment indicates this is not published when this is a variant
- if (urlSegment.IsNullOrWhiteSpace()) return null;
+ if (urlSegment.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
if (hasDomains == false && hideTopLevelNode.Value)
+ {
ApplyHideTopLevelNodeFromPath(node, pathParts, preview);
+ }
// assemble the route
pathParts.Reverse();
@@ -182,7 +209,8 @@ private string GetRouteByIdInternal(bool preview, int contentId, bool? hideTopLe
return route;
}
- private IPublishedContent FollowRoute(IPublishedContent content, IReadOnlyList parts, int start, string culture)
+ private IPublishedContent FollowRoute(IPublishedContent content, IReadOnlyList parts, int start,
+ string culture)
{
var i = start;
while (content != null && i < parts.Count)
@@ -194,6 +222,7 @@ private IPublishedContent FollowRoute(IPublishedContent content, IReadOnlyList GetAtRoot(bool preview, string cu
{
// handle context culture for variant
if (culture == null)
+ {
culture = _variationContextAccessor?.VariationContext?.Culture ?? "";
+ }
// _snapshot.GetAtRoot() returns all ContentNode at root
// both .Draft and .Published cannot be null at the same time
@@ -272,13 +316,15 @@ public override IEnumerable GetAtRoot(bool preview, string cu
// GetNodePublishedContent may return null if !preview and there is no
// published model, so we need to filter these nulls out
- var atRoot = _snapshot.GetAtRoot()
+ IEnumerable atRoot = _snapshot.GetAtRoot()
.Select(n => GetNodePublishedContent(n, preview))
.WhereNotNull();
// if a culture is specified, we must ensure that it is avail/published
if (culture != "*")
+ {
atRoot = atRoot.Where(x => x.IsInvariantOrHasCulture(culture));
+ }
return atRoot;
}
@@ -286,7 +332,9 @@ public override IEnumerable GetAtRoot(bool preview, string cu
private static IPublishedContent GetNodePublishedContent(ContentNode node, bool preview)
{
if (node == null)
+ {
return null;
+ }
// both .Draft and .Published cannot be null at the same time
@@ -299,7 +347,10 @@ private static IPublishedContent GetNodePublishedContent(ContentNode node, bool
// this is for published content when previewing
private static IPublishedContent GetPublishedContentAsDraft(IPublishedContent content /*, bool preview*/)
{
- if (content == null /*|| preview == false*/) return null; //content;
+ if (content == null /*|| preview == false*/)
+ {
+ return null; //content;
+ }
// an object in the cache is either an IPublishedContentOrMedia,
// or a model inheriting from PublishedContentExtended - in which
@@ -309,12 +360,10 @@ private static IPublishedContent GetPublishedContentAsDraft(IPublishedContent co
return inner.AsDraft();
}
- public override bool HasContent(bool preview)
- {
- return preview
+ public override bool HasContent(bool preview) =>
+ preview
? _snapshot.IsEmpty == false
: _snapshot.GetAtRoot().Any(x => x.PublishedModel != null);
- }
#endregion
@@ -322,21 +371,24 @@ public override bool HasContent(bool preview)
public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars)
{
- var navigator = CreateNavigator(preview);
- var iterator = navigator.Select(xpath, vars);
+ XPathNavigator navigator = CreateNavigator(preview);
+ XPathNodeIterator iterator = navigator.Select(xpath, vars);
return GetSingleByXPath(iterator);
}
public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
{
- var navigator = CreateNavigator(preview);
- var iterator = navigator.Select(xpath, vars);
+ XPathNavigator navigator = CreateNavigator(preview);
+ XPathNodeIterator iterator = navigator.Select(xpath, vars);
return GetSingleByXPath(iterator);
}
private static IPublishedContent GetSingleByXPath(XPathNodeIterator iterator)
{
- if (iterator.MoveNext() == false) return null;
+ if (iterator.MoveNext() == false)
+ {
+ return null;
+ }
var xnav = iterator.Current as NavigableNavigator;
var xcontent = xnav?.UnderlyingObject as NavigableContent;
@@ -345,15 +397,16 @@ private static IPublishedContent GetSingleByXPath(XPathNodeIterator iterator)
public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars)
{
- var navigator = CreateNavigator(preview);
- var iterator = navigator.Select(xpath, vars);
+ XPathNavigator navigator = CreateNavigator(preview);
+ XPathNodeIterator iterator = navigator.Select(xpath, vars);
return GetByXPath(iterator);
}
- public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
+ public override IEnumerable GetByXPath(bool preview, XPathExpression xpath,
+ XPathVariable[] vars)
{
- var navigator = CreateNavigator(preview);
- var iterator = navigator.Select(xpath, vars);
+ XPathNavigator navigator = CreateNavigator(preview);
+ XPathNodeIterator iterator = navigator.Select(xpath, vars);
return GetByXPath(iterator);
}
@@ -364,7 +417,10 @@ private static IEnumerable GetByXPath(XPathNodeIterator itera
{
var xnav = iterator.Current as NavigableNavigator;
var xcontent = xnav?.UnderlyingObject as NavigableContent;
- if (xcontent == null) continue;
+ if (xcontent == null)
+ {
+ continue;
+ }
yield return xcontent.InnerContent;
}
@@ -395,14 +451,5 @@ public override XPathNavigator CreateNodeNavigator(int id, bool preview)
public override IPublishedContentType GetContentType(Guid key) => _snapshot.GetContentType(key);
#endregion
-
- #region IDisposable
-
- public void Dispose()
- {
- _snapshot.Dispose();
- }
-
- #endregion
}
}
diff --git a/src/Umbraco.PublishedCache.NuCache/ContentNode.cs b/src/Umbraco.PublishedCache.NuCache/ContentNode.cs
index 23088df3adc6..beb598686163 100644
--- a/src/Umbraco.PublishedCache.NuCache/ContentNode.cs
+++ b/src/Umbraco.PublishedCache.NuCache/ContentNode.cs
@@ -175,13 +175,6 @@ private IPublishedContent GetModel(ref IPublishedContent model, ContentData cont
public IPublishedContent PublishedModel => GetModel(ref _publishedModel, _publishedData);
public ContentNodeKit ToKit()
- => new ContentNodeKit
- {
- Node = this,
- ContentTypeId = ContentType.Id,
-
- DraftData = _draftData,
- PublishedData = _publishedData
- };
+ => new ContentNodeKit(this, ContentType.Id, _draftData, _publishedData);
}
}
diff --git a/src/Umbraco.PublishedCache.NuCache/ContentNodeKit.cs b/src/Umbraco.PublishedCache.NuCache/ContentNodeKit.cs
index bb05e1470661..3f230925fe34 100644
--- a/src/Umbraco.PublishedCache.NuCache/ContentNodeKit.cs
+++ b/src/Umbraco.PublishedCache.NuCache/ContentNodeKit.cs
@@ -1,23 +1,39 @@
-using Umbraco.Cms.Core.Models.PublishedContent;
+using System;
+using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
namespace Umbraco.Cms.Infrastructure.PublishedCache
{
- // what's needed to actually build a content node
public struct ContentNodeKit
{
+ [Obsolete("This will be changed to a property in future versions")]
public ContentNode Node;
+
+ [Obsolete("This will be changed to a property in future versions")]
public int ContentTypeId;
+
+ [Obsolete("This will be changed to a property in future versions")]
public ContentData DraftData;
+
+ [Obsolete("This will be changed to a property in future versions")]
public ContentData PublishedData;
+ public ContentNodeKit(ContentNode node, int contentTypeId, ContentData draftData, ContentData publishedData)
+ {
+ Node = node;
+ ContentTypeId = contentTypeId;
+ DraftData = draftData;
+ PublishedData = publishedData;
+ }
+
+
public bool IsEmpty => Node == null;
public bool IsNull => ContentTypeId < 0;
public static ContentNodeKit Empty { get; } = new ContentNodeKit();
- public static ContentNodeKit Null { get; } = new ContentNodeKit { ContentTypeId = -1 };
+ public static ContentNodeKit Null { get; } = new ContentNodeKit(null, -1, null, null);
public void Build(
IPublishedContentType contentType,
@@ -41,12 +57,9 @@ public void Build(
}
public ContentNodeKit Clone(IPublishedModelFactory publishedModelFactory)
- => new ContentNodeKit
- {
- ContentTypeId = ContentTypeId,
- DraftData = DraftData,
- PublishedData = PublishedData,
- Node = new ContentNode(Node, publishedModelFactory)
- };
+ => new ContentNodeKit(new ContentNode(Node, publishedModelFactory), ContentTypeId, DraftData, PublishedData);
+
+ public ContentNodeKit Clone(IPublishedModelFactory publishedModelFactory, ContentData draftData, ContentData publishedData)
+ => new ContentNodeKit(new ContentNode(Node, publishedModelFactory), ContentTypeId, draftData, publishedData);
}
}
diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs
index 6e6d6e33aa22..240e6c8861a6 100644
--- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs
+++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs
@@ -104,7 +104,11 @@ public ContentStore(
private class WriteLockInfo
{
+#pragma warning disable IDE1006 // Naming Styles
+
+ // This is a field that is used for ref operations
public bool Taken;
+#pragma warning restore IDE1006 // Naming Styles
}
// a scope contextual that represents a locked writer to the dictionary
diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentDataSerializer.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentDataSerializer.cs
index 99ce9365fcf6..ad093f229241 100644
--- a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentDataSerializer.cs
+++ b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentDataSerializer.cs
@@ -13,27 +13,25 @@ public ContentDataSerializer(IDictionaryOfPropertyDataSerializer dictionaryOfPro
_dictionaryOfPropertyDataSerializer = dictionaryOfPropertyDataSerializer;
if(_dictionaryOfPropertyDataSerializer == null)
{
- _dictionaryOfPropertyDataSerializer = DefaultPropertiesSerializer;
+ _dictionaryOfPropertyDataSerializer = s_defaultPropertiesSerializer;
}
}
- private static readonly DictionaryOfPropertyDataSerializer DefaultPropertiesSerializer = new DictionaryOfPropertyDataSerializer();
- private static readonly DictionaryOfCultureVariationSerializer DefaultCultureVariationsSerializer = new DictionaryOfCultureVariationSerializer();
+ private static readonly DictionaryOfPropertyDataSerializer s_defaultPropertiesSerializer = new DictionaryOfPropertyDataSerializer();
+ private static readonly DictionaryOfCultureVariationSerializer s_defaultCultureVariationsSerializer = new DictionaryOfCultureVariationSerializer();
private readonly IDictionaryOfPropertyDataSerializer _dictionaryOfPropertyDataSerializer;
public ContentData ReadFrom(Stream stream)
{
- return new ContentData
- {
- Published = PrimitiveSerializer.Boolean.ReadFrom(stream),
- Name = PrimitiveSerializer.String.ReadFrom(stream),
- UrlSegment = PrimitiveSerializer.String.ReadFrom(stream),
- VersionId = PrimitiveSerializer.Int32.ReadFrom(stream),
- VersionDate = PrimitiveSerializer.DateTime.ReadFrom(stream),
- WriterId = PrimitiveSerializer.Int32.ReadFrom(stream),
- TemplateId = PrimitiveSerializer.Int32.ReadFrom(stream),
- Properties = _dictionaryOfPropertyDataSerializer.ReadFrom(stream), // TODO: We don't want to allocate empty arrays
- CultureInfos = DefaultCultureVariationsSerializer.ReadFrom(stream) // TODO: We don't want to allocate empty arrays
- };
+ var published = PrimitiveSerializer.Boolean.ReadFrom(stream);
+ var name = PrimitiveSerializer.String.ReadFrom(stream);
+ var urlSegment = PrimitiveSerializer.String.ReadFrom(stream);
+ var versionId = PrimitiveSerializer.Int32.ReadFrom(stream);
+ var versionDate = PrimitiveSerializer.DateTime.ReadFrom(stream);
+ var writerId = PrimitiveSerializer.Int32.ReadFrom(stream);
+ var templateId = PrimitiveSerializer.Int32.ReadFrom(stream);
+ var properties = _dictionaryOfPropertyDataSerializer.ReadFrom(stream); // TODO: We don't want to allocate empty arrays
+ var cultureInfos = s_defaultCultureVariationsSerializer.ReadFrom(stream); // TODO: We don't want to allocate empty arrays
+ return new ContentData(name, urlSegment, versionId, versionDate, writerId, templateId, published, properties, cultureInfos);
}
public void WriteTo(ContentData value, Stream stream)
@@ -49,7 +47,7 @@ public void WriteTo(ContentData value, Stream stream)
PrimitiveSerializer.Int32.WriteTo(value.TemplateId.Value, stream);
}
_dictionaryOfPropertyDataSerializer.WriteTo(value.Properties, stream);
- DefaultCultureVariationsSerializer.WriteTo(value.CultureInfos, stream);
+ s_defaultCultureVariationsSerializer.WriteTo(value.CultureInfos, stream);
}
}
}
diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentNodeKitSerializer.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentNodeKitSerializer.cs
index db0886ce792a..42da3e601ba0 100644
--- a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentNodeKitSerializer.cs
+++ b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentNodeKitSerializer.cs
@@ -1,4 +1,4 @@
-using System.IO;
+using System.IO;
using CSharpTest.Net.Serialization;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
@@ -10,19 +10,17 @@ public ContentNodeKitSerializer(ContentDataSerializer contentDataSerializer = nu
_contentDataSerializer = contentDataSerializer;
if(_contentDataSerializer == null)
{
- _contentDataSerializer = DefaultDataSerializer;
+ _contentDataSerializer = s_defaultDataSerializer;
}
}
- static readonly ContentDataSerializer DefaultDataSerializer = new ContentDataSerializer();
+ static readonly ContentDataSerializer s_defaultDataSerializer = new ContentDataSerializer();
private readonly ContentDataSerializer _contentDataSerializer;
//static readonly ListOfIntSerializer ChildContentIdsSerializer = new ListOfIntSerializer();
public ContentNodeKit ReadFrom(Stream stream)
{
- var kit = new ContentNodeKit
- {
- Node = new ContentNode(
+ var contentNode = new ContentNode(
PrimitiveSerializer.Int32.ReadFrom(stream), // id
PrimitiveSerializer.Guid.ReadFrom(stream), // uid
PrimitiveSerializer.Int32.ReadFrom(stream), // level
@@ -31,15 +29,27 @@ public ContentNodeKit ReadFrom(Stream stream)
PrimitiveSerializer.Int32.ReadFrom(stream), // parent id
PrimitiveSerializer.DateTime.ReadFrom(stream), // date created
PrimitiveSerializer.Int32.ReadFrom(stream) // creator id
- ),
- ContentTypeId = PrimitiveSerializer.Int32.ReadFrom(stream)
- };
+ );
+
+ int contentTypeId = PrimitiveSerializer.Int32.ReadFrom(stream);
var hasDraft = PrimitiveSerializer.Boolean.ReadFrom(stream);
+ ContentData draftData = null;
+ ContentData publishedData = null;
if (hasDraft)
- kit.DraftData = _contentDataSerializer.ReadFrom(stream);
+ {
+ draftData = _contentDataSerializer.ReadFrom(stream);
+ }
var hasPublished = PrimitiveSerializer.Boolean.ReadFrom(stream);
if (hasPublished)
- kit.PublishedData = _contentDataSerializer.ReadFrom(stream);
+ {
+ publishedData = _contentDataSerializer.ReadFrom(stream);
+ }
+ var kit = new ContentNodeKit(
+ contentNode,
+ contentTypeId,
+ draftData,
+ publishedData);
+
return kit;
}
diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs
index a4fd45546899..a461cda437ee 100644
--- a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs
+++ b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs
@@ -8,19 +8,38 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
///
public class ContentData
{
- public string Name { get; set; }
- public string UrlSegment { get; set; }
- public int VersionId { get; set; }
- public DateTime VersionDate { get; set; }
- public int WriterId { get; set; }
- public int? TemplateId { get; set; }
- public bool Published { get; set; }
+ [Obsolete("Use ctor with all params, as the pros should be immutable")]
+ public ContentData()
+ {
- public IDictionary Properties { get; set; }
+ }
+
+ public ContentData(string name, string urlSegment, int versionId, DateTime versionDate, int writerId, int? templateId, bool published, IDictionary properties, IReadOnlyDictionary cultureInfos)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ UrlSegment = urlSegment;
+ VersionId = versionId;
+ VersionDate = versionDate;
+ WriterId = writerId;
+ TemplateId = templateId;
+ Published = published;
+ Properties = properties ?? throw new ArgumentNullException(nameof(properties));
+ CultureInfos = cultureInfos;
+ }
+
+ public string Name { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
+ public string UrlSegment { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
+ public int VersionId { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
+ public DateTime VersionDate { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
+ public int WriterId { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
+ public int? TemplateId { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
+ public bool Published { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
+
+ public IDictionary Properties { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
///
/// The collection of language Id to name for the content item
///
- public IReadOnlyDictionary CultureInfos { get; set; }
+ public IReadOnlyDictionary CultureInfos { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
}
}
diff --git a/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs b/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs
new file mode 100644
index 000000000000..61f10917fd3c
--- /dev/null
+++ b/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs
@@ -0,0 +1,15 @@
+using System.Linq;
+using Umbraco.Cms.Core.PublishedCache;
+
+namespace Umbraco.Cms.Infrastructure.PublishedCache
+{
+ public static class DomainCacheExtensions
+ {
+ public static bool GetAssignedWithCulture(this IDomainCache domainCache, string culture, int documentId, bool includeWildcards = false)
+ {
+ var assigned = domainCache.GetAssigned(documentId, includeWildcards);
+
+ return culture is null ? assigned.Any() : assigned.Any(x => x.Culture == culture);
+ }
+ }
+}
diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs
index 2919edb8c345..649bc0eebbe6 100644
--- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs
+++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs
@@ -820,18 +820,16 @@ private ContentNodeKit CreateContentNodeKit(ContentSourceDto dto, IContentCacheD
bool published = false;
var deserializedContent = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published);
- d = new ContentData
- {
- Name = dto.EditName,
- Published = published,
- TemplateId = dto.EditTemplateId,
- VersionId = dto.VersionId,
- VersionDate = dto.EditVersionDate,
- WriterId = dto.EditWriterId,
- Properties = deserializedContent.PropertyData, // TODO: We don't want to allocate empty arrays
- CultureInfos = deserializedContent.CultureData,
- UrlSegment = deserializedContent.UrlSegment
- };
+ d = new ContentData(
+ dto.EditName,
+ deserializedContent.UrlSegment,
+ dto.VersionId,
+ dto.EditVersionDate,
+ dto.EditWriterId,
+ dto.EditTemplateId,
+ published,
+ deserializedContent.PropertyData,
+ deserializedContent.CultureData);
}
}
@@ -851,31 +849,23 @@ private ContentNodeKit CreateContentNodeKit(ContentSourceDto dto, IContentCacheD
bool published = true;
var deserializedContent = serializer.Deserialize(dto, dto.PubData, dto.PubDataRaw, published);
- p = new ContentData
- {
- Name = dto.PubName,
- UrlSegment = deserializedContent.UrlSegment,
- Published = published,
- TemplateId = dto.PubTemplateId,
- VersionId = dto.VersionId,
- VersionDate = dto.PubVersionDate,
- WriterId = dto.PubWriterId,
- Properties = deserializedContent.PropertyData, // TODO: We don't want to allocate empty arrays
- CultureInfos = deserializedContent.CultureData
- };
+ p = new ContentData(
+ dto.PubName,
+ deserializedContent.UrlSegment,
+ dto.VersionId,
+ dto.PubVersionDate,
+ dto.PubWriterId,
+ dto.PubTemplateId,
+ published,
+ deserializedContent.PropertyData,
+ deserializedContent.CultureData);
}
}
var n = new ContentNode(dto.Id, dto.Key,
dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId);
- var s = new ContentNodeKit
- {
- Node = n,
- ContentTypeId = dto.ContentTypeId,
- DraftData = d,
- PublishedData = p
- };
+ var s = new ContentNodeKit(n, dto.ContentTypeId, d, p);
return s;
}
@@ -888,27 +878,21 @@ private ContentNodeKit CreateMediaNodeKit(ContentSourceDto dto, IContentCacheDat
bool published = true;
var deserializedMedia = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published);
- var p = new ContentData
- {
- Name = dto.EditName,
- Published = published,
- TemplateId = -1,
- VersionId = dto.VersionId,
- VersionDate = dto.EditVersionDate,
- WriterId = dto.CreatorId, // what-else?
- Properties = deserializedMedia.PropertyData, // TODO: We don't want to allocate empty arrays
- CultureInfos = deserializedMedia.CultureData
- };
+ var p = new ContentData(
+ dto.EditName,
+ null,
+ dto.VersionId,
+ dto.EditVersionDate,
+ dto.CreatorId,
+ -1,
+ published,
+ deserializedMedia.PropertyData,
+ deserializedMedia.CultureData);
var n = new ContentNode(dto.Id, dto.Key,
dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId);
- var s = new ContentNodeKit
- {
- Node = n,
- ContentTypeId = dto.ContentTypeId,
- PublishedData = p
- };
+ var s = new ContentNodeKit(n, dto.ContentTypeId, null, p);
return s;
}
diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs b/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs
index 4796f3229534..53cc597cf5b1 100644
--- a/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs
+++ b/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs
@@ -34,15 +34,8 @@ public static IPublishedContent Create(
IVariationContextAccessor variationContextAccessor,
IPublishedModelFactory publishedModelFactory)
{
- var d = new ContentData
- {
- Name = member.Name,
- Published = previewing,
- TemplateId = -1,
- VersionDate = member.UpdateDate,
- WriterId = member.CreatorId, // what else?
- Properties = GetPropertyValues(contentType, member)
- };
+ var d = new ContentData(member.Name, null, 0, member.UpdateDate, member.CreatorId, -1, previewing, GetPropertyValues(contentType, member), null);
+
var n = new ContentNode(
member.Id,
member.Key,
diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
index 83d2bd5ccbfe..20df72608076 100644
--- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
+++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
@@ -46,7 +46,6 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
private readonly IPublishedModelFactory _publishedModelFactory;
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
private readonly IHostingEnvironment _hostingEnvironment;
- private readonly IContentCacheDataSerializerFactory _contentCacheDataSerializerFactory;
private readonly ContentDataSerializer _contentDataSerializer;
private readonly NuCacheSettings _config;
@@ -93,7 +92,6 @@ public PublishedSnapshotService(
IPublishedModelFactory publishedModelFactory,
IHostingEnvironment hostingEnvironment,
IOptions config,
- IContentCacheDataSerializerFactory contentCacheDataSerializerFactory,
ContentDataSerializer contentDataSerializer)
{
_options = options;
@@ -111,7 +109,6 @@ public PublishedSnapshotService(
_defaultCultureAccessor = defaultCultureAccessor;
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
- _contentCacheDataSerializerFactory = contentCacheDataSerializerFactory;
_contentDataSerializer = contentDataSerializer;
_config = config.Value;
_publishedModelFactory = publishedModelFactory;
diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs
index acfac62741b7..0190d5d40074 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net.Mime;
using System.Text;
@@ -16,12 +17,11 @@
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Extensions;
-using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
///
- /// Am abstract API controller providing functionality used for dealing with content and media types
+ /// Am abstract API controller providing functionality used for dealing with content and media types
///
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[PrefixlessBodyModelValidator]
@@ -39,13 +39,15 @@ protected ContentTypeControllerBase(
IUmbracoMapper umbracoMapper,
ILocalizedTextService localizedTextService)
{
- _editorValidatorCollection = editorValidatorCollection ?? throw new ArgumentNullException(nameof(editorValidatorCollection));
+ _editorValidatorCollection = editorValidatorCollection ??
+ throw new ArgumentNullException(nameof(editorValidatorCollection));
CultureDictionary = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary));
ContentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService));
MediaTypeService = mediaTypeService ?? throw new ArgumentNullException(nameof(mediaTypeService));
MemberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
UmbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
- LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
+ LocalizedTextService =
+ localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
}
protected ICultureDictionary CultureDictionary { get; }
@@ -56,22 +58,26 @@ protected ContentTypeControllerBase(
public ILocalizedTextService LocalizedTextService { get; }
///
- /// Returns the available composite content types for a given content type
+ /// Returns the available composite content types for a given content type
///
///
///
- /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out
- /// along with any content types that have matching property types that are included in the filtered content types
+ /// This is normally an empty list but if additional content type aliases are passed in, any content types containing
+ /// those aliases will be filtered out
+ /// along with any content types that have matching property types that are included in the filtered content types
///
///
- /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out.
- /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot
- /// be looked up via the db, they need to be passed in.
+ /// This is normally an empty list but if additional property type aliases are passed in, any content types that have
+ /// these aliases will be filtered out.
+ /// This is required because in the case of creating/modifying a content type because new property types being added to
+ /// it are not yet persisted so cannot
+ /// be looked up via the db, they need to be passed in.
///
///
/// Whether the composite content types should be applicable for an element type
///
- protected ActionResult>> PerformGetAvailableCompositeContentTypes(int contentTypeId,
+ protected ActionResult>> PerformGetAvailableCompositeContentTypes(
+ int contentTypeId,
UmbracoObjectTypes type,
string[] filterContentTypes,
string[] filterPropertyTypes,
@@ -89,26 +95,38 @@ protected ActionResult>> PerformGetAvailabl
if (contentTypeId > 0)
{
source = ContentTypeService.Get(contentTypeId);
- if (source == null) return NotFound();
+ if (source == null)
+ {
+ return NotFound();
+ }
}
+
allContentTypes = ContentTypeService.GetAll().Cast().ToArray();
break;
case UmbracoObjectTypes.MediaType:
if (contentTypeId > 0)
{
- source =MediaTypeService.Get(contentTypeId);
- if (source == null) return NotFound();
+ source = MediaTypeService.Get(contentTypeId);
+ if (source == null)
+ {
+ return NotFound();
+ }
}
- allContentTypes =MediaTypeService.GetAll().Cast().ToArray();
+
+ allContentTypes = MediaTypeService.GetAll().Cast().ToArray();
break;
case UmbracoObjectTypes.MemberType:
if (contentTypeId > 0)
{
source = MemberTypeService.Get(contentTypeId);
- if (source == null) return NotFound();
+ if (source == null)
+ {
+ return NotFound();
+ }
}
+
allContentTypes = MemberTypeService.GetAll().Cast().ToArray();
break;
@@ -116,16 +134,20 @@ protected ActionResult>> PerformGetAvailabl
throw new ArgumentOutOfRangeException("The entity type was not a content type");
}
- var availableCompositions = ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes, isElement);
+ ContentTypeAvailableCompositionsResults availableCompositions =
+ ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes,
+ filterPropertyTypes, isElement);
-
- var currCompositions = source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray();
+ IContentTypeComposition[] currCompositions =
+ source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray();
var compAliases = currCompositions.Select(x => x.Alias).ToArray();
- var ancestors = availableCompositions.Ancestors.Select(x => x.Alias);
+ IEnumerable ancestors = availableCompositions.Ancestors.Select(x => x.Alias);
return availableCompositions.Results
- .Select(x => new Tuple(UmbracoMapper.Map(x.Composition), x.Allowed))
+ .Select(x =>
+ new Tuple(UmbracoMapper.Map(x.Composition),
+ x.Allowed))
.Select(x =>
{
//we need to ensure that the item is enabled if it is already selected
@@ -139,9 +161,10 @@ protected ActionResult>> PerformGetAvailabl
//translate the name
x.Item1.Name = TranslateItem(x.Item1.Name);
- var contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key);
- var containers = GetEntityContainers(contentType, type)?.ToArray();
- var containerPath = $"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}";
+ IContentTypeComposition contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key);
+ EntityContainer[] containers = GetEntityContainers(contentType, type)?.ToArray();
+ var containerPath =
+ $"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}";
x.Item1.AdditionalData["containerPath"] = containerPath;
return x;
@@ -149,7 +172,8 @@ protected ActionResult>> PerformGetAvailabl
.ToList();
}
- private IEnumerable GetEntityContainers(IContentTypeComposition contentType, UmbracoObjectTypes type)
+ private IEnumerable GetEntityContainers(IContentTypeComposition contentType,
+ UmbracoObjectTypes type)
{
if (contentType == null)
{
@@ -170,12 +194,13 @@ private IEnumerable GetEntityContainers(IContentTypeComposition
}
///
- /// Returns a list of content types where a particular composition content type is used
+ /// Returns a list of content types where a particular composition content type is used
///
/// Type of content Type, eg documentType or mediaType
/// Id of composition content type
///
- protected ActionResult> PerformGetWhereCompositionIsUsedInContentTypes(int contentTypeId, UmbracoObjectTypes type)
+ protected ActionResult> PerformGetWhereCompositionIsUsedInContentTypes(
+ int contentTypeId, UmbracoObjectTypes type)
{
var id = 0;
@@ -190,7 +215,7 @@ protected ActionResult> PerformGetWhereCompositionIsUse
break;
case UmbracoObjectTypes.MediaType:
- source =MediaTypeService.Get(contentTypeId);
+ source = MediaTypeService.Get(contentTypeId);
break;
case UmbracoObjectTypes.MemberType:
@@ -202,7 +227,9 @@ protected ActionResult> PerformGetWhereCompositionIsUse
}
if (source == null)
+ {
return NotFound();
+ }
id = source.Id;
}
@@ -216,7 +243,7 @@ protected ActionResult> PerformGetWhereCompositionIsUse
break;
case UmbracoObjectTypes.MediaType:
- composedOf =MediaTypeService.GetComposedOf(id);
+ composedOf = MediaTypeService.GetComposedOf(id);
break;
case UmbracoObjectTypes.MemberType:
@@ -242,10 +269,14 @@ EntityBasic TranslateName(EntityBasic e)
protected string TranslateItem(string text)
{
if (text == null)
+ {
return null;
+ }
if (text.StartsWith("#") == false)
+ {
return text;
+ }
text = text.Substring(1);
return CultureDictionary[text].IfNullOrWhiteSpace(text);
@@ -261,18 +292,22 @@ protected ActionResult PerformPostSave 0 ? getContentType(ctId) : null;
- if (ctId > 0 && ct == null) return NotFound();
+ TContentType ct = ctId > 0 ? getContentType(ctId) : null;
+ if (ctId > 0 && ct == null)
+ {
+ return NotFound();
+ }
//Validate that there's no other ct with the same alias
// it in fact cannot be the same as any content type alias (member, content or media) because
// this would interfere with how ModelsBuilder works and also how many of the published caches
// works since that is based on aliases.
- var allAliases = ContentTypeService.GetAllContentTypeAliases();
+ IEnumerable allAliases = ContentTypeService.GetAllContentTypeAliases();
var exists = allAliases.InvariantContains(contentTypeSave.Alias);
if (exists && (ctId == 0 || !ct.Alias.InvariantEquals(contentTypeSave.Alias)))
{
- ModelState.AddModelError("Alias", LocalizedTextService.Localize("editcontenttype", "aliasAlreadyExists"));
+ ModelState.AddModelError("Alias",
+ LocalizedTextService.Localize("editcontenttype", "aliasAlreadyExists"));
}
// execute the external validators
@@ -280,13 +315,14 @@ protected ActionResult PerformPostSave(ctId, contentTypeSave, ct);
+ TContentTypeDisplay err =
+ CreateModelStateValidationEror(ctId, contentTypeSave, ct);
return ValidationProblem(err);
}
//filter out empty properties
contentTypeSave.Groups = contentTypeSave.Groups.Where(x => x.Name.IsNullOrWhiteSpace() == false).ToList();
- foreach (var group in contentTypeSave.Groups)
+ foreach (PropertyGroupBasic group in contentTypeSave.Groups)
{
group.Properties = group.Properties.Where(x => x.Alias.IsNullOrWhiteSpace() == false).ToList();
}
@@ -302,12 +338,22 @@ protected ActionResult PerformPostSave(ex, contentTypeSave, ct, ctId);
- if (responseEx != null) return ValidationProblem(responseEx);
+ TContentTypeDisplay responseEx =
+ CreateInvalidCompositionResponseException(
+ ex, contentTypeSave, ct, ctId);
+ if (responseEx != null)
+ {
+ return ValidationProblem(responseEx);
+ }
}
- var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, ct);
- if (exResult != null) return ValidationProblem(exResult);
+ TContentTypeDisplay exResult =
+ CreateCompositionValidationExceptionIfInvalid(
+ contentTypeSave, ct);
+ if (exResult != null)
+ {
+ return ValidationProblem(exResult);
+ }
saveContentType(ct);
@@ -329,7 +375,8 @@ protected ActionResult PerformPostSave x == 0);
- contentTypeSave.AllowedContentTypes = contentTypeSave.AllowedContentTypes.Where(x => x > 0).ToList();
+ contentTypeSave.AllowedContentTypes =
+ contentTypeSave.AllowedContentTypes.Where(x => x > 0).ToList();
}
//save as new
@@ -342,15 +389,24 @@ protected ActionResult PerformPostSave(ex, contentTypeSave, ct, ctId);
+ TContentTypeDisplay responseEx =
+ CreateInvalidCompositionResponseException(
+ ex, contentTypeSave, ct, ctId);
if (responseEx is null)
+ {
throw ex;
+ }
return ValidationProblem(responseEx);
}
- var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, newCt);
- if (exResult != null) return ValidationProblem(exResult);
+ TContentTypeDisplay exResult =
+ CreateCompositionValidationExceptionIfInvalid(
+ contentTypeSave, newCt);
+ if (exResult != null)
+ {
+ return ValidationProblem(exResult);
+ }
//set id to null to ensure its handled as a new type
contentTypeSave.Id = null;
@@ -364,30 +420,33 @@ protected ActionResult PerformPostSave x.ModelType == modelType)
- .SelectMany(x => x.Validate(model))
- .Where(x => !string.IsNullOrWhiteSpace(x.ErrorMessage) && x.MemberNames.Any());
+ IEnumerable validationResults = _editorValidatorCollection
+ .Where(x => x.ModelType == modelType)
+ .SelectMany(x => x.Validate(model))
+ .Where(x => !string.IsNullOrWhiteSpace(x.ErrorMessage) && x.MemberNames.Any());
- foreach (var r in validationResults)
- foreach (var m in r.MemberNames)
- modelState.AddModelError(m, r.ErrorMessage);
+ foreach (ValidationResult r in validationResults)
+ foreach (var m in r.MemberNames)
+ {
+ modelState.AddModelError(m, r.ErrorMessage);
+ }
}
///
- /// Move
+ /// Move
///
///
///
@@ -398,13 +457,13 @@ protected IActionResult PerformMove(
Func getContentType,
Func>> doMove)
{
- var toMove = getContentType(move.Id);
+ TContentType toMove = getContentType(move.Id);
if (toMove == null)
{
return NotFound();
}
- var result = doMove(toMove, move.ParentId);
+ Attempt> result = doMove(toMove, move.ParentId);
if (result.Success)
{
return Content(toMove.Path, MediaTypeNames.Text.Plain, Encoding.UTF8);
@@ -424,7 +483,7 @@ protected IActionResult PerformMove(
}
///
- /// Move
+ /// Move
///
///
///
@@ -435,13 +494,13 @@ protected IActionResult PerformCopy(
Func getContentType,
Func>> doCopy)
{
- var toMove = getContentType(move.Id);
+ TContentType toMove = getContentType(move.Id);
if (toMove == null)
{
return NotFound();
}
- var result = doCopy(toMove, move.ParentId);
+ Attempt> result = doCopy(toMove, move.ParentId);
if (result.Success)
{
return Content(toMove.Path, MediaTypeNames.Text.Plain, Encoding.UTF8);
@@ -461,31 +520,39 @@ protected IActionResult PerformCopy(
}
///
- /// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors
+ /// Validates the composition and adds errors to the model state if any are found then throws an error response if
+ /// there are errors
///
///
///
///
- private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition)
+ private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition)
where TContentTypeSave : ContentTypeSave
where TPropertyType : PropertyTypeBasic
where TContentTypeDisplay : ContentTypeCompositionDisplay
{
- var service = GetContentTypeService();
- var validateAttempt = service.ValidateComposition(composition);
+ IContentTypeBaseService service = GetContentTypeService();
+ Attempt validateAttempt = service.ValidateComposition(composition);
if (validateAttempt == false)
{
- //if it's not successful then we need to return some model state for the property aliases that
- // are duplicated
- var invalidPropertyAliases = validateAttempt.Result.Distinct();
- AddCompositionValidationErrors(contentTypeSave, invalidPropertyAliases);
+ // if it's not successful then we need to return some model state for the property type and property group
+ // aliases that are duplicated
+ IEnumerable duplicatePropertyTypeAliases = validateAttempt.Result.Distinct();
+ var invalidPropertyGroupAliases =
+ (validateAttempt.Exception as InvalidCompositionException)?.PropertyGroupAliases ??
+ Array.Empty();
+
+ AddCompositionValidationErrors(contentTypeSave,
+ duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
- var display = UmbracoMapper.Map(composition);
+ TContentTypeDisplay display = UmbracoMapper.Map(composition);
//map the 'save' data on top
display = UmbracoMapper.Map(contentTypeSave, display);
display.Errors = ModelState.ToErrorDictionary();
return display;
}
+
return null;
}
@@ -493,39 +560,62 @@ public IContentTypeBaseService GetContentTypeService()
where T : IContentTypeComposition
{
if (typeof(T).Implements())
+ {
return ContentTypeService as IContentTypeBaseService;
+ }
+
if (typeof(T).Implements())
+ {
return MediaTypeService as IContentTypeBaseService;
+ }
+
if (typeof(T).Implements())
+ {
return MemberTypeService as IContentTypeBaseService;
+ }
+
throw new ArgumentException("Type " + typeof(T).FullName + " does not have a service.");
}
///
- /// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors
+ /// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors
///
///
- ///
+ ///
+ ///
///
- private void AddCompositionValidationErrors(TContentTypeSave contentTypeSave, IEnumerable invalidPropertyAliases)
+ private void AddCompositionValidationErrors(TContentTypeSave contentTypeSave,
+ IEnumerable duplicatePropertyTypeAliases, IEnumerable invalidPropertyGroupAliases)
where TContentTypeSave : ContentTypeSave
where TPropertyType : PropertyTypeBasic
{
- foreach (var propertyAlias in invalidPropertyAliases)
+ foreach (var propertyTypeAlias in duplicatePropertyTypeAliases)
{
- // Find the property relating to these
- var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias);
- var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(property));
+ // Find the property type relating to these
+ TPropertyType property = contentTypeSave.Groups.SelectMany(x => x.Properties)
+ .Single(x => x.Alias == propertyTypeAlias);
+ PropertyGroupBasic group =
+ contentTypeSave.Groups.Single(x => x.Properties.Contains(property));
var propertyIndex = group.Properties.IndexOf(property);
var groupIndex = contentTypeSave.Groups.IndexOf(group);
var key = $"Groups[{groupIndex}].Properties[{propertyIndex}].Alias";
- ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions");
+ ModelState.AddModelError(key, "Duplicate property aliases aren't allowed between compositions");
+ }
+
+ foreach (var propertyGroupAlias in invalidPropertyGroupAliases)
+ {
+ // Find the property group relating to these
+ PropertyGroupBasic group =
+ contentTypeSave.Groups.Single(x => x.Alias == propertyGroupAlias);
+ var groupIndex = contentTypeSave.Groups.IndexOf(group);
+ var key = $"Groups[{groupIndex}].Name";
+ ModelState.AddModelError(key, "Different group types aren't allowed between compositions");
}
}
///
- /// If the exception is an InvalidCompositionException create a response exception to be thrown for validation errors
+ /// If the exception is an InvalidCompositionException create a response exception to be thrown for validation errors
///
///
///
@@ -535,7 +625,8 @@ private void AddCompositionValidationErrors(TCo
///
///
///
- private TContentTypeDisplay CreateInvalidCompositionResponseException(
+ private TContentTypeDisplay CreateInvalidCompositionResponseException(
Exception ex, TContentTypeSave contentTypeSave, TContentType ct, int ctId)
where TContentTypeDisplay : ContentTypeCompositionDisplay
where TContentTypeSave : ContentTypeSave
@@ -550,23 +641,27 @@ private TContentTypeDisplay CreateInvalidCompositionResponseException(contentTypeSave, invalidCompositionException.PropertyTypeAliases);
+ AddCompositionValidationErrors(contentTypeSave,
+ invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases);
return CreateModelStateValidationEror(ctId, contentTypeSave, ct);
}
+
return null;
}
///
- /// Used to throw the ModelState validation results when the ModelState is invalid
+ /// Used to throw the ModelState validation results when the ModelState is invalid
///
///
///
///
///
///
- private TContentTypeDisplay CreateModelStateValidationEror(int ctId, TContentTypeSave contentTypeSave, TContentType ct)
+ private TContentTypeDisplay CreateModelStateValidationEror(int ctId,
+ TContentTypeSave contentTypeSave, TContentType ct)
where TContentTypeDisplay : ContentTypeCompositionDisplay
where TContentTypeSave : ContentTypeSave
{
diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
index 2eaeee3d12ec..91af3724343f 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using System.Dynamic;
using System.Globalization;
using System.Linq;
+using System.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -13,6 +15,7 @@
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
+using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Models.TemplateQuery;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Security;
@@ -26,20 +29,22 @@
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.ModelBinders;
using Umbraco.Extensions;
-using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
///
- /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode
+ /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are
+ /// based on CMSNode
///
///
- ///
- /// This controller allows resolving basic entity data for various entities without placing the hard restrictions on users that may not have access
- /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all users. In some cases such as accessing
- /// Members, more explicit security checks are done.
- ///
- /// Some objects such as macros are not based on CMSNode
+ ///
+ /// This controller allows resolving basic entity data for various entities without placing the hard restrictions
+ /// on users that may not have access
+ /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all
+ /// users. In some cases such as accessing
+ /// Members, more explicit security checks are done.
+ ///
+ /// Some objects such as macros are not based on CMSNode
///
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[ParameterSwapControllerActionSelector(nameof(GetAncestors), "id", typeof(int), typeof(Guid))]
@@ -51,26 +56,28 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[ParameterSwapControllerActionSelector(nameof(GetUrl), "id", typeof(int), typeof(Udi))]
public class EntityController : UmbracoAuthorizedJsonController
{
- private readonly ITreeService _treeService;
- private readonly UmbracoTreeSearcher _treeSearcher;
- private readonly SearchableTreeCollection _searchableTreeCollection;
- private readonly IPublishedContentQuery _publishedContentQuery;
- private readonly IShortStringHelper _shortStringHelper;
- private readonly IEntityService _entityService;
+ private static readonly string[] _postFilterSplitStrings = { "=", "==", "!=", "<>", ">", "<", ">=", "<=" };
+
+ private readonly AppCaches _appCaches;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
- private readonly IPublishedUrlProvider _publishedUrlProvider;
private readonly IContentService _contentService;
- private readonly IUmbracoMapper _umbracoMapper;
+ private readonly IContentTypeService _contentTypeService;
private readonly IDataTypeService _dataTypeService;
- private readonly ISqlContext _sqlContext;
- private readonly ILocalizedTextService _localizedTextService;
+ private readonly IEntityService _entityService;
private readonly IFileService _fileService;
- private readonly IContentTypeService _contentTypeService;
- private readonly IMediaTypeService _mediaTypeService;
+ private readonly ILocalizationService _localizationService;
+ private readonly ILocalizedTextService _localizedTextService;
private readonly IMacroService _macroService;
+ private readonly IMediaTypeService _mediaTypeService;
+ private readonly IPublishedContentQuery _publishedContentQuery;
+ private readonly IPublishedUrlProvider _publishedUrlProvider;
+ private readonly SearchableTreeCollection _searchableTreeCollection;
+ private readonly IShortStringHelper _shortStringHelper;
+ private readonly ISqlContext _sqlContext;
+ private readonly UmbracoTreeSearcher _treeSearcher;
+ private readonly ITreeService _treeService;
+ private readonly IUmbracoMapper _umbracoMapper;
private readonly IUserService _userService;
- private readonly ILocalizationService _localizationService;
- private readonly AppCaches _appCaches;
public EntityController(
ITreeService treeService,
@@ -102,7 +109,8 @@ public EntityController(
publishedContentQuery ?? throw new ArgumentNullException(nameof(publishedContentQuery));
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper));
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
- _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
+ _backofficeSecurityAccessor = backofficeSecurityAccessor ??
+ throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
_publishedUrlProvider =
publishedUrlProvider ?? throw new ArgumentNullException(nameof(publishedUrlProvider));
_contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
@@ -120,16 +128,19 @@ public EntityController(
_appCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches));
}
+
///
- /// Returns an Umbraco alias given a string
+ /// Returns an Umbraco alias given a string
///
///
///
///
public dynamic GetSafeAlias(string value, bool camelCase = true)
{
- var returnValue = string.IsNullOrWhiteSpace(value) ? string.Empty : value.ToSafeAlias(_shortStringHelper, camelCase);
- dynamic returnObj = new System.Dynamic.ExpandoObject();
+ var returnValue = string.IsNullOrWhiteSpace(value)
+ ? string.Empty
+ : value.ToSafeAlias(_shortStringHelper, camelCase);
+ dynamic returnObj = new ExpandoObject();
returnObj.alias = returnValue;
returnObj.original = value;
returnObj.camelCase = camelCase;
@@ -138,41 +149,47 @@ public dynamic GetSafeAlias(string value, bool camelCase = true)
}
///
- /// Searches for results based on the entity type
+ /// Searches for results based on the entity type
///
///
///
///
- /// A starting point for the search, generally a node id, but for members this is a member type alias
+ /// A starting point for the search, generally a node id, but for members this is a member type alias
///
/// If set used to look up whether user and group start node permissions will be ignored.
///
[HttpGet]
- public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeKey = null)
+ public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null,
+ Guid? dataTypeKey = null)
{
// NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that
if (string.IsNullOrEmpty(query))
+ {
return Enumerable.Empty();
+ }
//TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type
- var ignoreUserStartNodes = dataTypeKey.HasValue && _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
+ var ignoreUserStartNodes = dataTypeKey.HasValue &&
+ _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes);
}
///
- /// Searches for all content that the user is allowed to see (based on their allowed sections)
+ /// Searches for all content that the user is allowed to see (based on their allowed sections)
///
///
///
///
- /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need
- /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search
- /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result.
- ///
- /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search
- /// methods might be used in things like pickers in the content editor.
+ /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to
+ /// edit, we need
+ /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the
+ /// global search
+ /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result.
+ /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those
+ /// search
+ /// methods might be used in things like pickers in the content editor.
///
[HttpGet]
public IDictionary SearchAll(string query)
@@ -180,16 +197,22 @@ public IDictionary SearchAll(string query)
var result = new Dictionary();
if (string.IsNullOrEmpty(query))
+ {
return result;
+ }
var allowedSections = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.AllowedSections.ToArray();
- foreach (var searchableTree in _searchableTreeCollection.SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder))
+ foreach (KeyValuePair searchableTree in _searchableTreeCollection
+ .SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder))
{
if (allowedSections.Contains(searchableTree.Value.AppAlias))
{
- var tree = _treeService.GetByAlias(searchableTree.Key);
- if (tree == null) continue; //shouldn't occur
+ Tree tree = _treeService.GetByAlias(searchableTree.Key);
+ if (tree == null)
+ {
+ continue; //shouldn't occur
+ }
result[Tree.GetRootNodeDisplayName(tree, _localizedTextService)] = new TreeSearchResult
{
@@ -201,49 +224,52 @@ public IDictionary SearchAll(string query)
};
}
}
+
return result;
}
///
- /// Gets the path for a given node ID
+ /// Gets the path for a given node ID
///
///
///
///
public IConvertToActionResult GetPath(int id, UmbracoEntityTypes type)
{
- var foundContentResult = GetResultForId(id, type);
- var foundContent = foundContentResult.Value;
+ ActionResult foundContentResult = GetResultForId(id, type);
+ EntityBasic foundContent = foundContentResult.Value;
if (foundContent is null)
{
return foundContentResult;
}
- return new ActionResult>(foundContent.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(
- s => int.Parse(s, CultureInfo.InvariantCulture)));
+ return new ActionResult>(foundContent.Path
+ .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(
+ s => int.Parse(s, CultureInfo.InvariantCulture)));
}
///
- /// Gets the path for a given node ID
+ /// Gets the path for a given node ID
///
///
///
///
public IConvertToActionResult GetPath(Guid id, UmbracoEntityTypes type)
{
- var foundContentResult = GetResultForKey(id, type);
- var foundContent = foundContentResult.Value;
+ ActionResult foundContentResult = GetResultForKey(id, type);
+ EntityBasic foundContent = foundContentResult.Value;
if (foundContent is null)
{
return foundContentResult;
}
- return new ActionResult>(foundContent.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(
- s => int.Parse(s, CultureInfo.InvariantCulture)));
+ return new ActionResult>(foundContent.Path
+ .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(
+ s => int.Parse(s, CultureInfo.InvariantCulture)));
}
///
- /// Gets the path for a given node ID
+ /// Gets the path for a given node ID
///
///
///
@@ -260,16 +286,19 @@ public IActionResult GetPath(Udi id, UmbracoEntityTypes type)
}
///
- /// Gets the URL of an entity
+ /// Gets the URL of an entity
///
/// UDI of the entity to fetch URL for
/// The culture to fetch the URL for
/// The URL or path to the item
public IActionResult GetUrl(Udi id, string culture = "*")
{
- var intId = _entityService.GetId(id);
+ Attempt intId = _entityService.GetId(id);
if (!intId.Success)
+ {
return NotFound();
+ }
+
UmbracoEntityTypes entityType;
switch (id.EntityType)
{
@@ -285,18 +314,61 @@ public IActionResult GetUrl(Udi id, string culture = "*")
default:
return NotFound();
}
+
return GetUrl(intId.Result, entityType, culture);
}
///
- /// Gets the URL of an entity
+ /// Get entity URLs by UDIs
+ ///
+ ///
+ /// A list of UDIs to lookup items by
+ ///
+ /// The culture to fetch the URL for
+ /// Dictionary mapping Udi -> Url
+ ///
+ /// We allow for POST because there could be quite a lot of Ids.
+ ///
+ [HttpGet]
+ [HttpPost]
+ public IDictionary GetUrlsByUdis([FromJsonPath] Udi[] udis, string culture = null)
+ {
+ if (udis == null || udis.Length == 0)
+ {
+ return new Dictionary();
+ }
+
+ // TODO: PMJ 2021-09-27 - Should GetUrl(Udi) exist as an extension method on UrlProvider/IUrlProvider (in v9)
+ string MediaOrDocumentUrl(Udi udi)
+ {
+ if (udi is not GuidUdi guidUdi)
+ {
+ return null;
+ }
+
+ return guidUdi.EntityType switch
+ {
+ Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(guidUdi.Guid,
+ culture: culture ?? ClientCulture()),
+ // NOTE: If culture is passed here we get an empty string rather than a media item URL WAT
+ Constants.UdiEntityType.Media => _publishedUrlProvider.GetMediaUrl(guidUdi.Guid, culture: null),
+ _ => null
+ };
+ }
+
+ return udis
+ .Select(udi => new { Udi = udi, Url = MediaOrDocumentUrl(udi) }).ToDictionary(x => x.Udi, x => x.Url);
+ }
+
+ ///
+ /// Gets the URL of an entity
///
/// Int id of the entity to fetch URL for
/// The type of entity such as Document, Media, Member
/// The culture to fetch the URL for
/// The URL or path to the item
///
- /// We are not restricting this with security because there is no sensitive data
+ /// We are not restricting this with security because there is no sensitive data
///
public IActionResult GetUrl(int id, UmbracoEntityTypes type, string culture = null)
{
@@ -315,7 +387,7 @@ public IActionResult GetUrl(int id, UmbracoEntityTypes type, string culture = nu
}
}
- var ancestors = GetResultForAncestors(id, type);
+ IEnumerable ancestors = GetResultForAncestors(id, type);
//if content, skip the first node for replicating NiceUrl defaults
if (type == UmbracoEntityTypes.Document)
@@ -330,7 +402,7 @@ public IActionResult GetUrl(int id, UmbracoEntityTypes type, string culture = nu
///
- /// Gets an entity by a xpath query
+ /// Gets an entity by a xpath query
///
///
///
@@ -342,48 +414,53 @@ public ActionResult GetByQuery(string query, int nodeContextId, Umb
if (type != UmbracoEntityTypes.Document)
+ {
throw new ArgumentException("Get by query is only compatible with entities of type Document");
+ }
var q = ParseXPathQuery(query, nodeContextId);
- var node = _publishedContentQuery.ContentSingleAtXPath(q);
+ IPublishedContent node = _publishedContentQuery.ContentSingleAtXPath(q);
if (node == null)
+ {
return null;
+ }
return GetById(node.Id, type);
}
// PP: Work in progress on the query parser
- private string ParseXPathQuery(string query, int id)
- {
- return UmbracoXPathPathSyntaxParser.ParseXPathQuery(
- xpathExpression: query,
- nodeContextId: id,
- getPath: nodeid =>
+ private string ParseXPathQuery(string query, int id) =>
+ UmbracoXPathPathSyntaxParser.ParseXPathQuery(
+ query,
+ id,
+ nodeid =>
{
- var ent = _entityService.Get(nodeid);
+ IEntitySlim ent = _entityService.Get(nodeid);
return ent.Path.Split(Constants.CharArrays.Comma).Reverse();
},
- publishedContentExists: i => _publishedContentQuery.Content(i) != null);
- }
+ i => _publishedContentQuery.Content(i) != null);
[HttpGet]
public ActionResult GetUrlAndAnchors(Udi id, string culture = "*")
{
- var intId = _entityService.GetId(id);
+ Attempt intId = _entityService.GetId(id);
if (!intId.Success)
+ {
return NotFound();
+ }
return GetUrlAndAnchors(intId.Result, culture);
}
+
[HttpGet]
public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*")
{
culture = culture ?? ClientCulture();
var url = _publishedUrlProvider.GetUrl(id, culture: culture);
- var anchorValues = _contentService.GetAnchorValuesFromRTEs(id, culture);
+ IEnumerable anchorValues = _contentService.GetAnchorValuesFromRTEs(id, culture);
return new UrlAndAnchors(url, anchorValues);
}
@@ -391,137 +468,13 @@ public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*")
[HttpPost]
public IEnumerable GetAnchors(AnchorsModel model)
{
- var anchorValues = _contentService.GetAnchorValuesFromRTEContent(model.RteContent);
+ IEnumerable anchorValues = _contentService.GetAnchorValuesFromRTEContent(model.RteContent);
return anchorValues;
}
-
- #region GetById
-
- ///
- /// Gets an entity by it's id
- ///
- ///
- ///
- ///
- public ActionResult GetById(int id, UmbracoEntityTypes type)
- {
- return GetResultForId(id, type);
- }
-
- ///
- /// Gets an entity by it's key
- ///
- ///
- ///
- ///
- public ActionResult GetById(Guid id, UmbracoEntityTypes type)
- {
- return GetResultForKey(id, type);
- }
-
- ///
- /// Gets an entity by it's UDI
- ///
- ///
- ///
- ///
- public ActionResult GetById(Udi id, UmbracoEntityTypes type)
- {
- var guidUdi = id as GuidUdi;
- if (guidUdi != null)
- {
- return GetResultForKey(guidUdi.Guid, type);
- }
-
- return NotFound();
- }
- #endregion
-
- #region GetByIds
- ///
- /// Get entities by integer ids
- ///
- ///
- ///
- ///
- ///
- /// We allow for POST because there could be quite a lot of Ids
- ///
- [HttpGet]
- [HttpPost]
- public ActionResult> GetByIds([FromJsonPath]int[] ids, [FromQuery]UmbracoEntityTypes type)
- {
- if (ids == null)
- {
- return NotFound();
- }
-
- return new ActionResult>(GetResultForIds(ids, type));
- }
-
- ///
- /// Get entities by GUID ids
- ///
- ///
- ///
- ///
- ///
- /// We allow for POST because there could be quite a lot of Ids
- ///
- [HttpGet]
- [HttpPost]
- public ActionResult> GetByIds([FromJsonPath]Guid[] ids, [FromQuery]UmbracoEntityTypes type)
- {
- if (ids == null)
- {
- return NotFound();
- }
-
- return new ActionResult>(GetResultForKeys(ids, type));
- }
-
- ///
- /// Get entities by UDIs
- ///
- ///
- /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type!
- ///
- ///
- ///
- ///
- /// We allow for POST because there could be quite a lot of Ids.
- ///
- [HttpGet]
- [HttpPost]
- public ActionResult> GetByIds([FromJsonPath]Udi[] ids, [FromQuery]UmbracoEntityTypes type)
- {
- if (ids == null)
- {
- return NotFound();
- }
-
- if (ids.Length == 0)
- {
- return Enumerable.Empty().ToList();
- }
-
- //all udi types will need to be the same in this list so we'll determine by the first
- //currently we only support GuidUdi for this method
-
- var guidUdi = ids[0] as GuidUdi;
- if (guidUdi != null)
- {
- return new ActionResult>(GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type));
- }
-
- return NotFound();
- }
- #endregion
-
public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeKey = null)
{
- var objectType = ConvertToObjectType(type);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(type);
if (objectType.HasValue)
{
//TODO: Need to check for Object types that support hierarchy here, some might not.
@@ -531,11 +484,15 @@ public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Gui
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
// root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes
- if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
+ if (id == Constants.System.Root && startNodes.Length > 0 &&
+ startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
{
- var nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray();
+ IEntitySlim[] nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray();
if (nodes.Length == 0)
+ {
return Enumerable.Empty();
+ }
+
var pr = new List(nodes.Select(_umbracoMapper.Map));
return pr;
}
@@ -546,6 +503,7 @@ public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Gui
.WhereNotNull()
.Select(_umbracoMapper.Map);
}
+
//now we need to convert the unknown ones
switch (type)
{
@@ -553,12 +511,13 @@ public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Gui
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + type);
}
}
///
- /// Get paged child entities by id
+ /// Get paged child entities by id
///
///
///
@@ -602,7 +561,8 @@ public ActionResult> GetPagedChildren(
//the EntityService can search paged members from the root
intId = -1;
- return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeKey);
+ return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter,
+ dataTypeKey);
}
//the EntityService cannot search members of a certain type, this is currently not supported and would require
@@ -611,16 +571,14 @@ public ActionResult> GetPagedChildren(
//TODO: We should really fix this in the EntityService but if we don't we should allow the ISearchableTree for the members controller
// to be used for this search instead of the built in/internal searcher
- var searchResult = _treeSearcher.ExamineSearch(filter ?? "", type, pageSize, pageNumber - 1, out long total, null, id);
+ IEnumerable searchResult = _treeSearcher.ExamineSearch(filter ?? "", type, pageSize,
+ pageNumber - 1, out var total, null, id);
- return new PagedResult(total, pageNumber, pageSize)
- {
- Items = searchResult
- };
+ return new PagedResult(total, pageNumber, pageSize) { Items = searchResult };
}
///
- /// Get paged child entities by id
+ /// Get paged child entities by id
///
///
///
@@ -641,11 +599,16 @@ public ActionResult> GetPagedChildren(
Guid? dataTypeKey = null)
{
if (pageNumber <= 0)
+ {
return NotFound();
+ }
+
if (pageSize <= 0)
+ {
return NotFound();
+ }
- var objectType = ConvertToObjectType(type);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(type);
if (objectType.HasValue)
{
IEnumerable entities;
@@ -656,14 +619,25 @@ public ActionResult> GetPagedChildren(
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
// root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes
- if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
+ if (id == Constants.System.Root && startNodes.Length > 0 &&
+ startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
{
if (pageNumber > 0)
+ {
return new PagedResult(0, 0, 0);
- var nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray();
+ }
+
+ IEntitySlim[] nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray();
if (nodes.Length == 0)
+ {
return new PagedResult(0, 0, 0);
- if (pageSize < nodes.Length) pageSize = nodes.Length; // bah
+ }
+
+ if (pageSize < nodes.Length)
+ {
+ pageSize = nodes.Length; // bah
+ }
+
var pr = new PagedResult(nodes.Length, pageNumber, pageSize)
{
Items = nodes.Select(_umbracoMapper.Map)
@@ -672,7 +646,8 @@ public ActionResult> GetPagedChildren(
}
// else proceed as usual
- entities = _entityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords,
+ entities = _entityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize,
+ out totalRecords,
filter.IsNullOrWhiteSpace()
? null
: _sqlContext.Query().Where(x => x.Name.Contains(filter)),
@@ -689,7 +664,7 @@ public ActionResult> GetPagedChildren(
{
Items = entities.Select(source =>
{
- var target = _umbracoMapper.Map(source, context =>
+ EntityBasic target = _umbracoMapper.Map(source, context =>
{
context.SetCulture(culture);
context.SetCulture(culture);
@@ -712,7 +687,8 @@ public ActionResult> GetPagedChildren(
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + type);
}
}
@@ -721,9 +697,11 @@ private int[] GetStartNodes(UmbracoEntityTypes type)
switch (type)
{
case UmbracoEntityTypes.Document:
- return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches);
+ return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(
+ _entityService, _appCaches);
case UmbracoEntityTypes.Media:
- return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches);
+ return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(
+ _entityService, _appCaches);
default:
return Array.Empty();
}
@@ -740,14 +718,19 @@ public ActionResult> GetPagedDescendants(
Guid? dataTypeKey = null)
{
if (pageNumber <= 0)
+ {
return NotFound();
+ }
+
if (pageSize <= 0)
+ {
return NotFound();
+ }
// re-normalize since NULL can be passed in
filter = filter ?? string.Empty;
- var objectType = ConvertToObjectType(type);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(type);
if (objectType.HasValue)
{
IEnumerable entities;
@@ -757,20 +740,23 @@ public ActionResult> GetPagedDescendants(
{
// root is special: we reduce it to start nodes
- int[] aids = GetStartNodes(type);
+ var aids = GetStartNodes(type);
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes
- ? _entityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords,
+ ? _entityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize,
+ out totalRecords,
_sqlContext.Query().Where(x => x.Name.Contains(filter)),
- Ordering.By(orderBy, orderDirection), includeTrashed: false)
- : _entityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords,
+ Ordering.By(orderBy, orderDirection), false)
+ : _entityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize,
+ out totalRecords,
_sqlContext.Query().Where(x => x.Name.Contains(filter)),
Ordering.By(orderBy, orderDirection));
}
else
{
- entities = _entityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords,
+ entities = _entityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize,
+ out totalRecords,
_sqlContext.Query().Where(x => x.Name.Contains(filter)),
Ordering.By(orderBy, orderDirection));
}
@@ -797,20 +783,26 @@ public ActionResult> GetPagedDescendants(
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + type);
}
}
- private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue && _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
+ private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue &&
+ _dataTypeService
+ .IsDataTypeIgnoringUserStartNodes(
+ dataTypeKey.Value);
- public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings)
- {
- return GetResultForAncestors(id, type, queryStrings);
- }
+ public IEnumerable GetAncestors(int id, UmbracoEntityTypes type,
+ [ModelBinder(typeof(HttpQueryStringModelBinder))]
+ FormCollection queryStrings) =>
+ GetResultForAncestors(id, type, queryStrings);
- public ActionResult> GetAncestors(Guid id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings)
+ public ActionResult> GetAncestors(Guid id, UmbracoEntityTypes type,
+ [ModelBinder(typeof(HttpQueryStringModelBinder))]
+ FormCollection queryStrings)
{
- var entity = _entityService.Get(id);
+ IEntitySlim entity = _entityService.Get(id);
if (entity is null)
{
return NotFound();
@@ -820,22 +812,24 @@ public ActionResult> GetAncestors(Guid id, UmbracoEntit
}
///
- /// Searches for results based on the entity type
+ /// Searches for results based on the entity type
///
///
///
///
/// If set to true, user and group start node permissions will be ignored.
///
- private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false)
+ private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType,
+ string searchFrom = null, bool ignoreUserStartNodes = false)
{
var culture = ClientCulture();
- return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, culture, searchFrom, ignoreUserStartNodes);
+ return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, culture, searchFrom,
+ ignoreUserStartNodes);
}
private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
// TODO: Need to check for Object types that support hierarchic here, some might not.
@@ -844,6 +838,7 @@ private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes
.WhereNotNull()
.Select(MapEntities());
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -851,30 +846,37 @@ private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
- private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormCollection queryStrings = null)
+ private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType,
+ FormCollection queryStrings = null)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
// TODO: Need to check for Object types that support hierarchic here, some might not.
- var ids = _entityService.Get(id).Path.Split(Constants.CharArrays.Comma).Select(s => int.Parse(s, CultureInfo.InvariantCulture)).Distinct().ToArray();
+ var ids = _entityService.Get(id).Path.Split(Constants.CharArrays.Comma)
+ .Select(s => int.Parse(s, CultureInfo.InvariantCulture)).Distinct().ToArray();
- var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(queryStrings?.GetValue("dataTypeId"));
+ var ignoreUserStartNodes =
+ IsDataTypeIgnoringUserStartNodes(queryStrings?.GetValue("dataTypeId"));
if (ignoreUserStartNodes == false)
{
int[] aids = null;
switch (entityType)
{
case UmbracoEntityTypes.Document:
- aids = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches);
+ aids = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser
+ .CalculateContentStartNodeIds(_entityService, _appCaches);
break;
case UmbracoEntityTypes.Media:
- aids = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches);
+ aids =
+ _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(
+ _entityService, _appCaches);
break;
}
@@ -889,12 +891,14 @@ private IEnumerable GetResultForAncestors(int id, UmbracoEntityType
lids.Add(i);
continue;
}
+
if (aids.Contains(i))
{
lids.Add(i);
ok = true;
}
}
+
ids = lids.ToArray();
}
}
@@ -908,6 +912,7 @@ private IEnumerable GetResultForAncestors(int id, UmbracoEntityType
.OrderBy(x => x.Level)
.Select(MapEntities(culture));
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -917,28 +922,33 @@ private IEnumerable GetResultForAncestors(int id, UmbracoEntityType
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType)
{
if (keys.Length == 0)
+ {
return Enumerable.Empty();
+ }
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
- var entities = _entityService.GetAll(objectType.Value, keys)
+ IEnumerable entities = _entityService.GetAll(objectType.Value, keys)
.WhereNotNull()
.Select(MapEntities());
// entities are in "some" order, put them back in order
var xref = entities.ToDictionary(x => x.Key);
- var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null);
+ IEnumerable result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null)
+ .Where(x => x != null);
return result;
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -948,28 +958,33 @@ private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityType
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType)
{
if (ids.Length == 0)
+ {
return Enumerable.Empty();
+ }
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
- var entities = _entityService.GetAll(objectType.Value, ids)
+ IEnumerable entities = _entityService.GetAll(objectType.Value, ids)
.WhereNotNull()
.Select(MapEntities());
// entities are in "some" order, put them back in order
var xref = entities.ToDictionary(x => x.Id);
- var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null);
+ IEnumerable result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null)
+ .Where(x => x != null);
return result;
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -979,22 +994,25 @@ private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes e
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private ActionResult GetResultForKey(Guid key, UmbracoEntityTypes entityType)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
- var found = _entityService.Get(key, objectType.Value);
+ IEntitySlim found = _entityService.Get(key, objectType.Value);
if (found == null)
{
return NotFound();
}
+
return _umbracoMapper.Map(found);
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -1009,22 +1027,25 @@ private ActionResult GetResultForKey(Guid key, UmbracoEntityTypes e
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private ActionResult GetResultForId(int id, UmbracoEntityTypes entityType)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
- var found = _entityService.Get(id, objectType.Value);
+ IEntitySlim found = _entityService.Get(id, objectType.Value);
if (found == null)
{
return NotFound();
}
+
return MapEntity(found);
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -1039,7 +1060,8 @@ private ActionResult GetResultForId(int id, UmbracoEntityTypes enti
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
@@ -1070,145 +1092,151 @@ private ActionResult GetResultForId(int id, UmbracoEntityTypes enti
}
///
- ///
///
/// The type of entity.
- /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are ignored.
+ ///
+ /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are
+ /// ignored.
+ ///
///
- public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter)
- {
- return GetResultForAll(type, postFilter);
- }
+ public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter) =>
+ GetResultForAll(type, postFilter);
///
- /// Gets the result for the entity list based on the type
+ /// Gets the result for the entity list based on the type
///
///
/// A string where filter that will filter the results dynamically with linq - optional
///
private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
// TODO: Should we order this by something ?
- var entities = _entityService.GetAll(objectType.Value).WhereNotNull().Select(MapEntities());
+ IEnumerable entities =
+ _entityService.GetAll(objectType.Value).WhereNotNull().Select(MapEntities());
return ExecutePostFilter(entities, postFilter);
}
+
//now we need to convert the unknown ones
switch (entityType)
{
case UmbracoEntityTypes.Template:
- var templates = _fileService.GetTemplates();
- var filteredTemplates = ExecutePostFilter(templates, postFilter);
+ IEnumerable templates = _fileService.GetTemplates();
+ IEnumerable filteredTemplates = ExecutePostFilter(templates, postFilter);
return filteredTemplates.Select(MapEntities());
case UmbracoEntityTypes.Macro:
//Get all macros from the macro service
- var macros = _macroService.GetAll().WhereNotNull().OrderBy(x => x.Name);
- var filteredMacros = ExecutePostFilter(macros, postFilter);
+ IOrderedEnumerable macros = _macroService.GetAll().WhereNotNull().OrderBy(x => x.Name);
+ IEnumerable filteredMacros = ExecutePostFilter(macros, postFilter);
return filteredMacros.Select(MapEntities());
case UmbracoEntityTypes.PropertyType:
//get all document types, then combine all property types into one list
- var propertyTypes = _contentTypeService.GetAll().Cast()
- .Concat(_mediaTypeService.GetAll())
- .ToArray()
- .SelectMany(x => x.PropertyTypes)
- .DistinctBy(composition => composition.Alias);
- var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter);
+ IEnumerable propertyTypes = _contentTypeService.GetAll()
+ .Cast()
+ .Concat(_mediaTypeService.GetAll())
+ .ToArray()
+ .SelectMany(x => x.PropertyTypes)
+ .DistinctBy(composition => composition.Alias);
+ IEnumerable filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter);
return _umbracoMapper.MapEnumerable(filteredPropertyTypes);
case UmbracoEntityTypes.PropertyGroup:
//get all document types, then combine all property types into one list
- var propertyGroups = _contentTypeService.GetAll().Cast()
- .Concat(_mediaTypeService.GetAll())
- .ToArray()
- .SelectMany(x => x.PropertyGroups)
- .DistinctBy(composition => composition.Name);
- var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter);
+ IEnumerable propertyGroups = _contentTypeService.GetAll()
+ .Cast()
+ .Concat(_mediaTypeService.GetAll())
+ .ToArray()
+ .SelectMany(x => x.PropertyGroups)
+ .DistinctBy(composition => composition.Name);
+ IEnumerable filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter);
return _umbracoMapper.MapEnumerable(filteredpropertyGroups);
case UmbracoEntityTypes.User:
- var users = _userService.GetAll(0, int.MaxValue, out _);
- var filteredUsers = ExecutePostFilter(users, postFilter);
+ IEnumerable users = _userService.GetAll(0, int.MaxValue, out _);
+ IEnumerable filteredUsers = ExecutePostFilter(users, postFilter);
return _umbracoMapper.MapEnumerable(filteredUsers);
case UmbracoEntityTypes.Stylesheet:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on stylesheets is not currently supported");
+ }
return _fileService.GetStylesheets().Select(MapEntities());
case UmbracoEntityTypes.Script:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on scripts is not currently supported");
+ }
return _fileService.GetScripts().Select(MapEntities());
case UmbracoEntityTypes.PartialView:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on partial views is not currently supported");
+ }
return _fileService.GetPartialViews().Select(MapEntities());
case UmbracoEntityTypes.Language:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on languages is not currently supported");
+ }
return _localizationService.GetAllLanguages().Select(MapEntities());
case UmbracoEntityTypes.DictionaryItem:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on dictionary items is not currently supported");
+ }
return GetAllDictionaryItems();
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter)
{
- if (postFilter.IsNullOrWhiteSpace()) return entities;
+ if (postFilter.IsNullOrWhiteSpace())
+ {
+ return entities;
+ }
var postFilterConditions = postFilter.Split(Constants.CharArrays.Ampersand);
foreach (var postFilterCondition in postFilterConditions)
{
- var queryCondition = BuildQueryCondition(postFilterCondition);
+ QueryCondition queryCondition = BuildQueryCondition(postFilterCondition);
if (queryCondition != null)
{
- var whereClauseExpression = queryCondition.BuildCondition("x");
+ Expression> whereClauseExpression = queryCondition.BuildCondition("x");
entities = entities.Where(whereClauseExpression.Compile());
}
-
}
+
return entities;
}
- private static readonly string[] _postFilterSplitStrings = new[]
- {
- "=",
- "==",
- "!=",
- "<>",
- ">",
- "<",
- ">=",
- "<="
- };
-
private static QueryCondition BuildQueryCondition(string postFilter)
{
var postFilterParts = postFilter.Split(_postFilterSplitStrings, 2, StringSplitOptions.RemoveEmptyEntries);
@@ -1234,26 +1262,21 @@ private static QueryCondition BuildQueryCondition(string postFilter)
return null;
}
- var type = typeof(T);
- var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
+ Type type = typeof(T);
+ PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
if (property == null)
{
return null;
}
- var queryCondition = new QueryCondition()
+ var queryCondition = new QueryCondition
{
- Term = new OperatorTerm()
- {
- Operator = binaryOperator
- },
+ Term = new OperatorTerm { Operator = binaryOperator },
ConstraintValue = constraintValue,
- Property = new PropertyModel()
+ Property = new PropertyModel
{
- Alias = propertyName,
- Name = propertyName,
- Type = property.PropertyType.Name
+ Alias = propertyName, Name = propertyName, Type = property.PropertyType.Name
}
};
@@ -1274,14 +1297,141 @@ private EntityBasic MapEntity(object entity, string culture = null)
private string ClientCulture() => Request.ClientCulture();
+
+ #region GetById
+
+ ///
+ /// Gets an entity by it's id
+ ///
+ ///
+ ///
+ ///
+ public ActionResult GetById(int id, UmbracoEntityTypes type) => GetResultForId(id, type);
+
+ ///
+ /// Gets an entity by it's key
+ ///
+ ///
+ ///
+ ///
+ public ActionResult GetById(Guid id, UmbracoEntityTypes type) => GetResultForKey(id, type);
+
+ ///
+ /// Gets an entity by it's UDI
+ ///
+ ///
+ ///
+ ///
+ public ActionResult GetById(Udi id, UmbracoEntityTypes type)
+ {
+ var guidUdi = id as GuidUdi;
+ if (guidUdi != null)
+ {
+ return GetResultForKey(guidUdi.Guid, type);
+ }
+
+ return NotFound();
+ }
+
+ #endregion
+
+ #region GetByIds
+
+ ///
+ /// Get entities by integer ids
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// We allow for POST because there could be quite a lot of Ids
+ ///
+ [HttpGet]
+ [HttpPost]
+ public ActionResult> GetByIds([FromJsonPath] int[] ids,
+ [FromQuery] UmbracoEntityTypes type)
+ {
+ if (ids == null)
+ {
+ return NotFound();
+ }
+
+ return new ActionResult>(GetResultForIds(ids, type));
+ }
+
+ ///
+ /// Get entities by GUID ids
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// We allow for POST because there could be quite a lot of Ids
+ ///
+ [HttpGet]
+ [HttpPost]
+ public ActionResult> GetByIds([FromJsonPath] Guid[] ids,
+ [FromQuery] UmbracoEntityTypes type)
+ {
+ if (ids == null)
+ {
+ return NotFound();
+ }
+
+ return new ActionResult>(GetResultForKeys(ids, type));
+ }
+
+ ///
+ /// Get entities by UDIs
+ ///
+ ///
+ /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type!
+ ///
+ ///
+ ///
+ ///
+ /// We allow for POST because there could be quite a lot of Ids.
+ ///
+ [HttpGet]
+ [HttpPost]
+ public ActionResult> GetByIds([FromJsonPath] Udi[] ids,
+ [FromQuery] UmbracoEntityTypes type)
+ {
+ if (ids == null)
+ {
+ return NotFound();
+ }
+
+ if (ids.Length == 0)
+ {
+ return Enumerable.Empty().ToList();
+ }
+
+ //all udi types will need to be the same in this list so we'll determine by the first
+ //currently we only support GuidUdi for this method
+
+ var guidUdi = ids[0] as GuidUdi;
+ if (guidUdi != null)
+ {
+ return new ActionResult>(
+ GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type));
+ }
+
+ return NotFound();
+ }
+
+ #endregion
+
#region Methods to get all dictionary items
+
private IEnumerable GetAllDictionaryItems()
{
var list = new List();
- foreach (var dictionaryItem in _localizationService.GetRootDictionaryItems().OrderBy(DictionaryItemSort()))
+ foreach (IDictionaryItem dictionaryItem in _localizationService.GetRootDictionaryItems()
+ .OrderBy(DictionaryItemSort()))
{
- var item = _umbracoMapper.Map(dictionaryItem);
+ EntityBasic item = _umbracoMapper.Map