From 908589277a4f0d4bb7b22fe06c2d42958adf34e5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 18 Apr 2018 19:46:47 +0200 Subject: [PATCH] U4-11227 - default variations and fallback (wip) --- .../PublishedContent/IPublishedProperty.cs | 8 +- .../IPublishedValueFallback.cs | 35 +++ .../IPublishedVariationContextAccessor.cs | 13 ++ .../PublishedContentExtensionsForModels.cs | 5 + .../PublishedContent/PublishedPropertyBase.cs | 8 +- .../PublishedVariationContext.cs | 30 +++ .../PublishedContent/RawValueProperty.cs | 8 +- ...ulturePublishedVariationContextAccessor.cs | 23 ++ ...StaticPublishedVariationContextAccessor.cs | 23 ++ src/Umbraco.Core/Umbraco.Core.csproj | 5 + .../Published/NestedContentTests.cs | 8 +- .../PublishedContentOtherTests.cs | 212 ++++++++++++++++++ .../SolidPublishedSnapshot.cs | 8 +- .../Routing/RenderRouteHandlerTests.cs | 1 + .../Scoping/ScopedNuCacheTests.cs | 3 + .../TestHelpers/TestObjects-Mocks.cs | 1 + .../TestHelpers/TestWithDatabaseBase.cs | 4 +- .../Accessors}/NoHttpContextAccessor.cs | 2 +- .../TestPublishedSnapshotAccessor.cs | 2 +- .../TestPublishedVariationContextAccessor.cs | 13 ++ .../Accessors}/TestUmbracoContextAccessor.cs | 2 +- .../Testing/TestingTests/MockTests.cs | 1 + src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 1 + src/Umbraco.Tests/Umbraco.Tests.csproj | 8 +- ...RenderIndexActionSelectorAttributeTests.cs | 1 + .../Web/Mvc/RenderModelBinderTests.cs | 1 + .../Web/Mvc/SurfaceControllerTests.cs | 1 + .../Web/Mvc/UmbracoViewPageTests.cs | 13 +- .../Web/TemplateUtilitiesTests.cs | 1 + .../Web/WebExtensionMethodTests.cs | 1 + .../PublishedCache/NuCache/ContentNode.cs | 17 +- .../PublishedCache/NuCache/ContentNodeKit.cs | 4 +- .../PublishedCache/NuCache/ContentStore.cs | 10 +- .../{Database.cs => DatabaseDataSource.cs} | 6 +- .../NuCache/DataSource/IDataSource.cs | 21 ++ .../PublishedCache/NuCache/MemberCache.cs | 12 +- .../NuCache/NuCacheComponent.cs | 13 +- .../PublishedCache/NuCache/Property.cs | 33 ++- .../NuCache/PublishedContent.cs | 7 +- .../PublishedCache/NuCache/PublishedMember.cs | 8 +- .../NuCache/PublishedSnapshotService.cs | 22 +- .../PublishedElementPropertyBase.cs | 8 +- .../PublishedSnapshotServiceBase.cs | 5 +- .../PublishedSnapshotService.cs | 14 +- .../XmlPublishedCache/XmlCacheComponent.cs | 1 + .../XmlPublishedCache/XmlPublishedProperty.cs | 6 +- src/Umbraco.Web/PublishedContentExtensions.cs | 47 ++-- .../PublishedContentPropertyExtension.cs | 27 +-- src/Umbraco.Web/PublishedElementExtensions.cs | 100 +++------ src/Umbraco.Web/Routing/PublishedRouter.cs | 4 +- src/Umbraco.Web/Umbraco.Web.csproj | 3 +- src/Umbraco.Web/umbraco.presentation/page.cs | 8 +- 52 files changed, 590 insertions(+), 228 deletions(-) create mode 100644 src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/IPublishedVariationContextAccessor.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedVariationContext.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/ThreadCulturePublishedVariationContextAccessor.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/ThreadStaticPublishedVariationContextAccessor.cs create mode 100644 src/Umbraco.Tests/PublishedContent/PublishedContentOtherTests.cs rename src/Umbraco.Tests/{TestHelpers => Testing/Objects/Accessors}/NoHttpContextAccessor.cs (78%) rename src/Umbraco.Tests/{TestHelpers/Stubs => Testing/Objects/Accessors}/TestPublishedSnapshotAccessor.cs (79%) create mode 100644 src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedVariationContextAccessor.cs rename src/Umbraco.Tests/{TestHelpers/Stubs => Testing/Objects/Accessors}/TestUmbracoContextAccessor.cs (77%) rename src/Umbraco.Web/PublishedCache/NuCache/DataSource/{Database.cs => DatabaseDataSource.cs} (97%) create mode 100644 src/Umbraco.Web/PublishedCache/NuCache/DataSource/IDataSource.cs diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs index 9d2cca3e6d..bfe1389921 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -21,7 +21,7 @@ /// Other caches that get their raw value from the database would consider that a property has "no /// value" if it is missing, null, or an empty string (including whitespace-only). /// - bool HasValue(string culture = null, string segment = null); + bool HasValue(string culture = ".", string segment = "."); /// /// Gets the source value of the property. @@ -35,7 +35,7 @@ /// If you're using that value, you're probably wrong, unless you're doing some internal /// Umbraco stuff. /// - object GetSourceValue(string culture = null, string segment = null); + object GetSourceValue(string culture = ".", string segment = "."); /// /// Gets the object value of the property. @@ -45,7 +45,7 @@ /// It can be null, or any type of CLR object. /// It has been fully prepared and processed by the appropriate converter. /// - object GetValue(string culture = null, string segment = null); + object GetValue(string culture = ".", string segment = "."); /// /// Gets the XPath value of the property. @@ -55,6 +55,6 @@ /// It must be either null, or a string, or an XPathNavigator. /// It has been fully prepared and processed by the appropriate converter. /// - object GetXPathValue(string culture = null, string segment = null); + object GetXPathValue(string culture = ".", string segment = "."); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs new file mode 100644 index 0000000000..d83438ab06 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs @@ -0,0 +1,35 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides a fallback strategy for getting values. + /// + public interface IPublishedValueFallback + { + /// + /// Gets a value. + /// + /// + /// This is invoked when getting a value for the specified and + /// could not return a value, and fallback rules should apply to get the value for another language and/or segment. + /// + TValue GetValue(IPublishedProperty property, string culture, string segment); + } + + // fixme question + // this is working at property level at the moment, should we move it up to element, + // so that the decision can be made based upon the entire element, other properties, etc? + // or, would we need the *two* levels? + + /// + /// Provides a default implementation of that does not fall back at all. + /// + public class NoPublishedValueFallback : IPublishedValueFallback + { + /// + public TValue GetValue(IPublishedProperty property, string culture, string segment) + { + // we don't implement fallback + return default; + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedVariationContextAccessor.cs new file mode 100644 index 0000000000..2af4230665 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedVariationContextAccessor.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Gives access to the current . + /// + public interface IPublishedVariationContextAccessor + { + /// + /// Gets or sets the current . + /// + PublishedVariationContext Context { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs index 7f42c022a5..df3213eb07 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs @@ -18,6 +18,11 @@ namespace Umbraco.Core.Models.PublishedContent if (content == null) return null; + // in order to provide a nice, "fluent" experience, this extension method + // needs to access Current, which is not always initialized in tests - not + // very elegant, but works + if (!Current.HasContainer) return content; + // get model // if factory returns nothing, throw var model = Current.PublishedModelFactory.CreateModel(content); diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index 7e2a5b5498..c6626be1b2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -53,15 +53,15 @@ namespace Umbraco.Core.Models.PublishedContent public string Alias => PropertyType.Alias; /// - public abstract bool HasValue(string culture = null, string segment = null); + public abstract bool HasValue(string culture = ".", string segment = "."); /// - public abstract object GetSourceValue(string culture = null, string segment = null); + public abstract object GetSourceValue(string culture = ".", string segment = "."); /// - public abstract object GetValue(string culture = null, string segment = null); + public abstract object GetValue(string culture = ".", string segment = "."); /// - public abstract object GetXPathValue(string culture = null, string segment = null); + public abstract object GetXPathValue(string culture = ".", string segment = "."); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedVariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedVariationContext.cs new file mode 100644 index 0000000000..2440b1dc32 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedVariationContext.cs @@ -0,0 +1,30 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents the published variation context. + /// + /// + /// The published variation context indicates which variation is the current default variation. + /// + public class PublishedVariationContext + { + /// + /// Initializes a new instance of the class. + /// + public PublishedVariationContext(string culture = null, string segment = null) + { + Culture = culture; + Segment = segment; + } + + /// + /// Gets the culture. + /// + public string Culture { get; set; } + + /// + /// Gets the segment. + /// + public string Segment { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs index e20d8cb49c..00a6b940bb 100644 --- a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -20,19 +20,19 @@ namespace Umbraco.Core.Models.PublishedContent private readonly Lazy _objectValue; private readonly Lazy _xpathValue; - public override object GetSourceValue(string culture = null, string segment = null) + public override object GetSourceValue(string culture = ".", string segment = ".") => culture == null & segment == null ? _sourceValue : null; - public override bool HasValue(string culture = null, string segment = null) + public override bool HasValue(string culture = ".", string segment = ".") { var sourceValue = GetSourceValue(culture, segment); return sourceValue is string s ? !string.IsNullOrWhiteSpace(s) : sourceValue != null; } - public override object GetValue(string culture = null, string segment = null) + public override object GetValue(string culture = ".", string segment = ".") => culture == null & segment == null ? _objectValue.Value : null; - public override object GetXPathValue(string culture = null, string segment = null) + public override object GetXPathValue(string culture = ".", string segment = ".") => culture == null & segment == null ? _xpathValue.Value : null; public RawValueProperty(PublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) diff --git a/src/Umbraco.Core/Models/PublishedContent/ThreadCulturePublishedVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/ThreadCulturePublishedVariationContextAccessor.cs new file mode 100644 index 0000000000..8bf02e3f9b --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/ThreadCulturePublishedVariationContextAccessor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides a CurrentUICulture-based implementation of . + /// + /// + /// This accessor does not support segments. There is no need to set the current context. + /// + public class ThreadCulturePublishedVariationContextAccessor : IPublishedVariationContextAccessor + { + private readonly ConcurrentDictionary _contexts = new ConcurrentDictionary(); + + public PublishedVariationContext Context + { + get => _contexts.GetOrAdd(Thread.CurrentThread.CurrentUICulture.Name, culture => new PublishedVariationContext { Culture = culture }); + set => throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/ThreadStaticPublishedVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/ThreadStaticPublishedVariationContextAccessor.cs new file mode 100644 index 0000000000..b7391e8b0d --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/ThreadStaticPublishedVariationContextAccessor.cs @@ -0,0 +1,23 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides a ThreadStatic-based implementation of . + /// + /// + /// Something must set the current context. + /// + public class ThreadStaticPublishedVariationContextAccessor : IPublishedVariationContextAccessor + { + [ThreadStatic] + private static PublishedVariationContext _context; + + /// + public PublishedVariationContext Context + { + get => _context; + set => _context = value; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2a4a79180b..7d6f27083b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -369,6 +369,11 @@ + + + + + diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index c806930704..2ad5f470ef 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -242,10 +242,10 @@ namespace Umbraco.Tests.Published _owner = owner; } - public override bool HasValue(string culture = null, string segment = null) => _hasValue; - public override object GetSourceValue(string culture = null, string segment = null) => _sourceValue; - public override object GetValue(string culture = null, string segment = null) => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview); - public override object GetXPathValue(string culture = null, string segment = null) => throw new WontImplementException(); + public override bool HasValue(string culture = ".", string segment = ".") => _hasValue; + public override object GetSourceValue(string culture = ".", string segment = ".") => _sourceValue; + public override object GetValue(string culture = ".", string segment = ".") => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview); + public override object GetXPathValue(string culture = ".", string segment = ".") => throw new WontImplementException(); } class TestPublishedContent : PublishedContentBase diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentOtherTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentOtherTests.cs new file mode 100644 index 0000000000..a131076a3a --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentOtherTests.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Stubs; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; +using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.PublishedCache.NuCache.DataSource; + +namespace Umbraco.Tests.PublishedContent +{ + [TestFixture] + public class PublishedContentOtherTests // FIXME rename! + { + [Test] + public void Test() + { + SettingsForTests.ConfigureSettings(SettingsForTests.GenerateMockUmbracoSettings()); + var globalSettings = UmbracoConfig.For.GlobalSettings(); + + // fixme - missing variant names here, and what else? + var kit = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(1, Guid.NewGuid(), 0, "-1,1", 0, -1, DateTime.Now, 0), + DraftData = new ContentData { Name="It Works2!", Published = false, TemplateId = 0, VersionId = 2, VersionDate = DateTime.Now, WriterId = 0, + Properties = new Dictionary { { "prop", new[] + { + new PropertyData { Value = "val2" }, + new PropertyData { Culture = "fr-FR", Value = "val-fr2" }, + new PropertyData { Culture = "en-UK", Value = "val-uk2" } + } } } }, + PublishedData = new ContentData { Name="It Works1!", Published = true, TemplateId = 0, VersionId = 1, VersionDate = DateTime.Now, WriterId = 0, + Properties = new Dictionary { { "prop", new[] + { + new PropertyData { Value = "val1" }, + new PropertyData { Culture = "fr-FR", Value = "val-fr1" }, + new PropertyData { Culture = "en-UK", Value = "val-uk1" } + } } } } + }; + + var dataSource = new TestDataSource(kit); + + var runtime = Mock.Of(); + Mock.Get(runtime).Setup(x => x.Level).Returns(RuntimeLevel.Run); + + var propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.InvariantNeutral | ContentVariation.CultureNeutral }; + var contentType = new ContentType(-1) { Id = 2, Alias = "alias-ct", Variations = ContentVariation.InvariantNeutral | ContentVariation.CultureNeutral }; + contentType.AddPropertyType(propertyType); + + var contentTypes = new[] + { + contentType + }; + + var dataType = new DataType(new VoidEditor("Editor", Mock.Of())) { Id = 3 }; + + var dataTypes = new[] + { + dataType + }; + + var contentTypeService = Mock.Of(); + Mock.Get(contentTypeService).Setup(x => x.GetAll()).Returns(contentTypes); + + var dataTypeService = Mock.Of(); + Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes); + + var serviceContext = new ServiceContext( + dataTypeService : dataTypeService, + + memberTypeService: Mock.Of(), + memberService: Mock.Of(), + + contentTypeService : contentTypeService, + + localizationService: Mock.Of() + ); + + var contentTypeFactory = new PublishedContentTypeFactory( + Mock.Of(), + new PropertyValueConverterCollection(Array.Empty()), + dataTypeService); + + var documentRepository = Mock.Of(); + var mediaRepository = Mock.Of(); + var memberRepository = Mock.Of(); + + var snapshotAccessor = new TestPublishedSnapshotAccessor(); + + var scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.CreateScope( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(() => Mock.Of()); + + var variationAccessor = new TestPublishedVariationContextAccessor(); + + var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; + var snapshotService = new PublishedSnapshotService(options, + null, + runtime, + serviceContext, + contentTypeFactory, + null, + snapshotAccessor, + variationAccessor, + Mock.Of(), + scopeProvider, + documentRepository, + mediaRepository, + memberRepository, + dataSource, + globalSettings); + + var snapshot = snapshotService.CreatePublishedSnapshot(previewToken: null); + var publishedContent = snapshot.Content.GetById(1); + + // invariant is the current default + variationAccessor.Context = null; + + Assert.IsNotNull(publishedContent); + Assert.AreEqual("It Works1!", publishedContent.Name); + Assert.AreEqual("val1", publishedContent.Value("prop")); + Assert.AreEqual("val-fr1", publishedContent.Value("prop", "fr-FR")); + Assert.AreEqual("val-uk1", publishedContent.Value("prop", "en-UK")); + + var draftContent = snapshot.Content.GetById(true, 1); + Assert.AreEqual("It Works2!", draftContent.Name); + Assert.AreEqual("val2", draftContent.Value("prop")); + Assert.AreEqual("val-fr2", draftContent.Value("prop", "fr-FR")); + Assert.AreEqual("val-uk2", draftContent.Value("prop", "en-UK")); + + variationAccessor.Context = new PublishedVariationContext("fr-FR"); + Assert.AreEqual("val-fr1", publishedContent.Value("prop")); + variationAccessor.Context = new PublishedVariationContext("en-UK"); + Assert.AreEqual("val-uk1", publishedContent.Value("prop")); + + // invariant needs to be retrieved explicitely, when it's not default + Assert.AreEqual("val1", publishedContent.Value("prop", culture: null)); + + // then, test fallback + } + + internal class TestDataSource : IDataSource + { + private readonly Dictionary _kits; + + public TestDataSource(params ContentNodeKit[] kits) + : this((IEnumerable) kits) + { } + + public TestDataSource(IEnumerable kits) + { + _kits = kits.ToDictionary(x => x.Node.Id, x => x); + } + + public ContentNodeKit GetContentSource(IScope scope, int id) + => _kits.TryGetValue(id, out var kit) ? kit : default; + + public IEnumerable GetAllContentSources(IScope scope) + => _kits.Values; + + public IEnumerable GetBranchContentSources(IScope scope, int id) + { + throw new NotImplementedException(); + } + + public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids) + => _kits.Values.Where(x => ids.Contains(x.ContentTypeId)); + + public ContentNodeKit GetMediaSource(IScope scope, int id) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAllMediaSources(IScope scope) + { + throw new NotImplementedException(); + } + + public IEnumerable GetBranchMediaSources(IScope scope, int id) + { + throw new NotImplementedException(); + } + + public IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 4b7a131bd0..2be86640d7 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -258,10 +258,10 @@ namespace Umbraco.Tests.PublishedContent public bool SolidHasValue { get; set; } public object SolidXPathValue { get; set; } - public object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue; - public object GetValue(string culture = null, string segment = null) => SolidValue; - public object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue; - public bool HasValue(string culture = null, string segment = null) => SolidHasValue; + public object GetSourceValue(string culture = ".", string segment = ".") => SolidSourceValue; + public object GetValue(string culture = ".", string segment = ".") => SolidValue; + public object GetXPathValue(string culture = ".", string segment = ".") => SolidXPathValue; + public bool HasValue(string culture = ".", string segment = ".") => SolidHasValue; } [PublishedModel("ContentType2")] diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index d0b3622127..f722906053 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -18,6 +18,7 @@ using Umbraco.Web.WebApi; using Umbraco.Core.Strings; using Umbraco.Core.Composing; using Umbraco.Tests.Testing; +using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web.Runtime; using Current = Umbraco.Web.Composing.Current; diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index cc9813cdbd..70ff7d8341 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -25,6 +25,7 @@ using Umbraco.Web; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.PublishedCache.NuCache.DataSource; using Umbraco.Web.Routing; using Umbraco.Web.Security; @@ -88,9 +89,11 @@ namespace Umbraco.Tests.Scoping contentTypeFactory, null, publishedSnapshotAccessor, + Mock.Of(), Logger, ScopeProvider, documentRepository, mediaRepository, memberRepository, + new DatabaseDataSource(), SystemDefaultCultureProvider, Container.GetInstance(), new SiteDomainHelper()); } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index ee0dd38c45..4952360b6a 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Stubs; +using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 0fcd3c9295..cede329bd0 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -33,6 +33,7 @@ using Umbraco.Core.Migrations.Install; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; using Umbraco.Tests.Testing.Objects.AccessorsAndProviders; +using Umbraco.Tests.Testing.Objects.Accessors; namespace Umbraco.Tests.TestHelpers { @@ -264,11 +265,12 @@ namespace Umbraco.Tests.TestHelpers // testing=true so XmlStore will not use the file nor the database var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor); + var variationContextAccessor = new TestPublishedVariationContextAccessor(); var service = new PublishedSnapshotService( ServiceContext, Container.GetInstance(), ScopeProvider, - cache, publishedSnapshotAccessor, + cache, publishedSnapshotAccessor, variationContextAccessor, Container.GetInstance(), Container.GetInstance(), Container.GetInstance(), SystemDefaultCultureProvider, Logger, diff --git a/src/Umbraco.Tests/TestHelpers/NoHttpContextAccessor.cs b/src/Umbraco.Tests/Testing/Objects/Accessors/NoHttpContextAccessor.cs similarity index 78% rename from src/Umbraco.Tests/TestHelpers/NoHttpContextAccessor.cs rename to src/Umbraco.Tests/Testing/Objects/Accessors/NoHttpContextAccessor.cs index b77f8f828c..9b37389241 100644 --- a/src/Umbraco.Tests/TestHelpers/NoHttpContextAccessor.cs +++ b/src/Umbraco.Tests/Testing/Objects/Accessors/NoHttpContextAccessor.cs @@ -1,7 +1,7 @@ using System.Web; using Umbraco.Web; -namespace Umbraco.Tests.TestHelpers +namespace Umbraco.Tests.Testing.Objects.Accessors { public class NoHttpContextAccessor : IHttpContextAccessor { diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedSnapshotAccessor.cs b/src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedSnapshotAccessor.cs similarity index 79% rename from src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedSnapshotAccessor.cs rename to src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedSnapshotAccessor.cs index 3768803de2..c46915e3a0 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedSnapshotAccessor.cs +++ b/src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedSnapshotAccessor.cs @@ -1,6 +1,6 @@ using Umbraco.Web.PublishedCache; -namespace Umbraco.Tests.TestHelpers.Stubs +namespace Umbraco.Tests.Testing.Objects.Accessors { public class TestPublishedSnapshotAccessor : IPublishedSnapshotAccessor { diff --git a/src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedVariationContextAccessor.cs b/src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedVariationContextAccessor.cs new file mode 100644 index 0000000000..9327b462f1 --- /dev/null +++ b/src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedVariationContextAccessor.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Tests.Testing.Objects.Accessors +{ + /// + /// Provides an implementation of for tests. + /// + public class TestPublishedVariationContextAccessor : IPublishedVariationContextAccessor + { + /// + public PublishedVariationContext Context { get; set; } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestUmbracoContextAccessor.cs b/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs similarity index 77% rename from src/Umbraco.Tests/TestHelpers/Stubs/TestUmbracoContextAccessor.cs rename to src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs index eaf8912fda..da93218907 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestUmbracoContextAccessor.cs +++ b/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs @@ -1,6 +1,6 @@ using Umbraco.Web; -namespace Umbraco.Tests.TestHelpers.Stubs +namespace Umbraco.Tests.Testing.Objects.Accessors { public class TestUmbracoContextAccessor : IUmbracoContextAccessor { diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 59bc24ed10..9f06d0de17 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; +using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.Routing; using Umbraco.Web.Security; diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 003ba9ff4d..6f803516cf 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -31,6 +31,7 @@ using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.Services; using Umbraco.Examine; +using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web.Composing.CompositionRoots; using Umbraco.Web._Legacy.Actions; using Current = Umbraco.Core.Composing.Current; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 20bc181017..dc38dd54ee 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -129,6 +129,7 @@ + @@ -176,9 +177,10 @@ - + + @@ -195,10 +197,10 @@ - + - + diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index 1f5fe1a6e3..6e8b22378c 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Profiling; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; +using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web.Models; diff --git a/src/Umbraco.Tests/Web/Mvc/RenderModelBinderTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderModelBinderTests.cs index 3b60f1a7c5..901c737584 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderModelBinderTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderModelBinderTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.TestHelpers.Stubs; +using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Current = Umbraco.Web.Composing.Current; diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index 931cc57493..e467889831 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; +using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web.Mvc; diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index 295f42fee2..8e9e6a3e22 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -424,22 +424,23 @@ namespace Umbraco.Tests.Web.Mvc //var provider = new ScopeUnitOfWorkProvider(databaseFactory, new RepositoryFactory(Mock.Of())); var scopeProvider = TestObjects.GetScopeProvider(Mock.Of()); var factory = Mock.Of(); - _service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache, Enumerable.Empty(), null, - null, null, null, + _service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache, Enumerable.Empty(), + null, null, + null, null, null, new TestSystemDefaultCultureProvider(), Current.Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper(), null, true, false); // no events var http = GetHttpContextFactory(url, routeData).HttpContext; - - var globalSettings = TestObjects.GetGlobalSettings(); + + var globalSettings = TestObjects.GetGlobalSettings(); var ctx = new UmbracoContext( http, _service, new WebSecurity(http, Current.Services.UserService, globalSettings), TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, + Enumerable.Empty(), + globalSettings, Mock.Of()); //if (setSingleton) diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index d35b4e5823..165f103d43 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; +using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; diff --git a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs b/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs index cc97633cde..aa1fce8c85 100644 --- a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs +++ b/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; +using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index a7cc1e950c..de658daeec 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -33,10 +33,11 @@ namespace Umbraco.Web.PublishedCache.NuCache int parentContentId, DateTime createDate, int creatorId, ContentData draftData, ContentData publishedData, - IPublishedSnapshotAccessor publishedSnapshotAccessor) + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IPublishedVariationContextAccessor variationContextAccessor) : this(id, uid, level, path, sortOrder, parentContentId, createDate, creatorId) { - SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor); + SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor, variationContextAccessor); } // 2-phases ctor, phase 1 @@ -58,7 +59,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // two-phase ctor, phase 2 - public void SetContentTypeAndData(PublishedContentType contentType, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor) + public void SetContentTypeAndData(PublishedContentType contentType, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor) { ContentType = contentType; @@ -66,9 +67,9 @@ namespace Umbraco.Web.PublishedCache.NuCache throw new ArgumentException("Both draftData and publishedData cannot be null at the same time."); if (draftData != null) - Draft = new PublishedContent(this, draftData, publishedSnapshotAccessor).CreateModel(); + Draft = new PublishedContent(this, draftData, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); if (publishedData != null) - Published = new PublishedContent(this, publishedData, publishedSnapshotAccessor).CreateModel(); + Published = new PublishedContent(this, publishedData, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); } // clone parent @@ -97,7 +98,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // clone with new content type - public ContentNode(ContentNode origin, PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor) + public ContentNode(ContentNode origin, PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor) { Id = origin.Id; Uid = origin.Uid; @@ -112,8 +113,8 @@ namespace Umbraco.Web.PublishedCache.NuCache var originDraft = origin.Draft == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Draft); var originPublished = origin.Published == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Published); - Draft = originDraft == null ? null : new PublishedContent(this, originDraft._contentData, publishedSnapshotAccessor).CreateModel(); - Published = originPublished == null ? null : new PublishedContent(this, originPublished._contentData, publishedSnapshotAccessor).CreateModel(); + Draft = originDraft == null ? null : new PublishedContent(this, originDraft._contentData, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); + Published = originPublished == null ? null : new PublishedContent(this, originPublished._contentData, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); ChildContentIds = origin.ChildContentIds; // can be the *same* list FIXME oh really? } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs index 82bfc8766a..19163d5e8d 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs @@ -17,9 +17,9 @@ namespace Umbraco.Web.PublishedCache.NuCache public static ContentNodeKit Null { get; } = new ContentNodeKit { ContentTypeId = -1 }; - public void Build(PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor) + public void Build(PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor) { - Node.SetContentTypeAndData(contentType, DraftData, PublishedData, publishedSnapshotAccessor); + Node.SetContentTypeAndData(contentType, DraftData, PublishedData, publishedSnapshotAccessor, variationContextAccessor); } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 74613509ba..95482bb6b1 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -19,6 +19,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // SnapDictionary has unit tests to ensure it all works correctly private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IPublishedVariationContextAccessor _variationContextAccessor; private readonly ConcurrentDictionary> _contentNodes; private readonly ConcurrentDictionary> _contentRootNodes; private readonly ConcurrentDictionary> _contentTypesById; @@ -43,9 +44,10 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Ctor - public ContentStore(IPublishedSnapshotAccessor publishedSnapshotAccessor, ILogger logger, BPlusTree localDb = null) + public ContentStore(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor, ILogger logger, BPlusTree localDb = null) { _publishedSnapshotAccessor = publishedSnapshotAccessor; + _variationContextAccessor = variationContextAccessor; _logger = logger; _localDb = localDb; @@ -277,7 +279,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (node == null) continue; var contentTypeId = node.ContentType.Id; if (index.TryGetValue(contentTypeId, out PublishedContentType contentType) == false) continue; - SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor)); + SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor, _variationContextAccessor)); } } finally @@ -391,7 +393,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentNodes.TryGetValue(id, out LinkedNode link); if (link?.Value == null) continue; - var node = new ContentNode(link.Value, contentType, _publishedSnapshotAccessor); + var node = new ContentNode(link.Value, contentType, _publishedSnapshotAccessor, _variationContextAccessor); SetValueLocked(_contentNodes, id, node); if (_localDb != null) RegisterChange(id, node.ToKit()); } @@ -414,7 +416,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return false; // and use - kit.Build(link.Value, _publishedSnapshotAccessor); + kit.Build(link.Value, _publishedSnapshotAccessor, _variationContextAccessor); return true; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs similarity index 97% rename from src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs rename to src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs index 342ad5b59f..16f11aeafd 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource // fixme - use SqlTemplate for these queries else it's going to be horribly slow! // provides efficient database access for NuCache - internal class Database + internal class DatabaseDataSource : IDataSource { // we want arrays, we want them all loaded, not an enumerable @@ -186,7 +186,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource { if (Debugger.IsAttached) throw new Exception("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); - Current.Logger.Warn("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); + Current.Logger.Warn("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); } else { @@ -211,7 +211,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource { if (Debugger.IsAttached) throw new Exception("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding."); - Current.Logger.Warn("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding."); + Current.Logger.Warn("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding."); } else { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/IDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/IDataSource.cs new file mode 100644 index 0000000000..323d954980 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/IDataSource.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Umbraco.Core.Scoping; + +namespace Umbraco.Web.PublishedCache.NuCache.DataSource +{ + /// + /// Defines a data source for NuCache. + /// + internal interface IDataSource + { + ContentNodeKit GetContentSource(IScope scope, int id); + IEnumerable GetAllContentSources(IScope scope); + IEnumerable GetBranchContentSources(IScope scope, int id); + IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids); + + ContentNodeKit GetMediaSource(IScope scope, int id); + IEnumerable GetAllMediaSources(IScope scope); + IEnumerable GetBranchMediaSources(IScope scope, int id); + IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs index a8cf4a97c0..899fbd6eed 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web.PublishedCache.NuCache class MemberCache : IPublishedMemberCache, INavigableData { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + public readonly IPublishedVariationContextAccessor _variationContextAccessor; private readonly ICacheProvider _snapshotCache; private readonly IMemberService _memberService; private readonly IDataTypeService _dataTypeService; @@ -23,10 +24,11 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly PublishedContentTypeCache _contentTypeCache; private readonly bool _previewDefault; - public MemberCache(bool previewDefault, ICacheProvider snapshotCache, IMemberService memberService, IDataTypeService dataTypeService, ILocalizationService localizationService, PublishedContentTypeCache contentTypeCache, IPublishedSnapshotAccessor publishedSnapshotAccessor) + public MemberCache(bool previewDefault, ICacheProvider snapshotCache, IMemberService memberService, IDataTypeService dataTypeService, ILocalizationService localizationService, PublishedContentTypeCache contentTypeCache, IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor) { _snapshotCache = snapshotCache; _publishedSnapshotAccessor = publishedSnapshotAccessor; + _variationContextAccessor = variationContextAccessor; _memberService = memberService; _dataTypeService = dataTypeService; _localizationService = localizationService; @@ -63,14 +65,14 @@ namespace Umbraco.Web.PublishedCache.NuCache var member = _memberService.GetById(memberId); return member == null ? null - : PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor); + : PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, _variationContextAccessor); }); } private IPublishedContent /*IPublishedMember*/ GetById(IMember member, bool previewing) { return GetCacheItem(CacheKeys.MemberCacheMember("ById", _previewDefault, member.Id), () => - PublishedMember.Create(member, GetContentType(member.ContentTypeId), previewing, _publishedSnapshotAccessor)); + PublishedMember.Create(member, GetContentType(member.ContentTypeId), previewing, _publishedSnapshotAccessor, _variationContextAccessor)); } public IPublishedContent /*IPublishedMember*/ GetByProviderKey(object key) @@ -105,7 +107,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public IPublishedContent /*IPublishedMember*/ GetByMember(IMember member) { - return PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor); + return PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, _variationContextAccessor); } public IEnumerable GetAtRoot(bool preview) @@ -113,7 +115,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // because members are flat (not a tree) everything is at root // because we're loading everything... let's just not cache? var members = _memberService.GetAllMembers(); - return members.Select(m => PublishedMember.Create(m, GetContentType(m.ContentTypeId), preview, _publishedSnapshotAccessor)); + return members.Select(m => PublishedMember.Create(m, GetContentType(m.ContentTypeId), preview, _publishedSnapshotAccessor, _variationContextAccessor)); } public XPathNavigator CreateNavigator() diff --git a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs index 3482a6cf2c..1d497d73e0 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs @@ -1,11 +1,5 @@ -using Umbraco.Core; -using Umbraco.Core.Components; -using Umbraco.Core.Logging; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using LightInject; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Components; +using Umbraco.Web.PublishedCache.NuCache.DataSource; namespace Umbraco.Web.PublishedCache.NuCache { @@ -15,6 +9,9 @@ namespace Umbraco.Web.PublishedCache.NuCache { base.Compose(composition); + // register the NuCache database data source + composition.Container.Register(); + // register the NuCache published snapshot service // must register default options, required in the service ctor composition.Container.Register(factory => new PublishedSnapshotService.Options()); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs index f2e3355750..d2f37a1488 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly Guid _contentUid; private readonly bool _isPreviewing; private readonly bool _isMember; - private readonly IPublishedContent _content; + private readonly PublishedContent _content; private readonly object _locko = new object(); @@ -71,7 +71,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // clone for previewing as draft a published content that is published and has no draft - public Property(Property origin, IPublishedContent content) + public Property(Property origin, PublishedContent content) : base(origin.PropertyType, origin.ReferenceCacheLevel) { _sourceValue = origin._sourceValue; @@ -84,7 +84,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; } - public override bool HasValue(string culture = null, string segment = null) => _sourceValue != null + public override bool HasValue(string culture = ".", string segment = ".") => _sourceValue != null && (!(_sourceValue is string) || string.IsNullOrWhiteSpace((string) _sourceValue) == false); // used to cache the recursive *property* for this property @@ -166,8 +166,10 @@ namespace Umbraco.Web.PublishedCache.NuCache return vvalue.InterValue; } - public override object GetSourceValue(string culture = null, string segment = null) + public override object GetSourceValue(string culture = ".", string segment = ".") { + ContextualizeVariation(ref culture, ref segment); + if (culture == null && segment == null) return _sourceValue; @@ -178,8 +180,21 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public override object GetValue(string culture = null, string segment = null) + private void ContextualizeVariation(ref string culture, ref string segment) { + if (culture != "." && segment != ".") return; + + // use context values + var publishedVariationContext = _content.VariationContextAccessor?.Context; + if (culture == ".") culture = publishedVariationContext?.Culture; + if (segment == ".") segment = publishedVariationContext?.Segment; + } + + public override object GetValue(string culture = ".", string segment = ".") + { + ContextualizeVariation(ref culture, ref segment); + + object value; lock (_locko) { var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); @@ -190,12 +205,16 @@ namespace Umbraco.Web.PublishedCache.NuCache if (cacheValues.ObjectInitialized) return cacheValues.ObjectValue; cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); cacheValues.ObjectInitialized = true; - return cacheValues.ObjectValue; + value = cacheValues.ObjectValue; } + + return value; } - public override object GetXPathValue(string culture = null, string segment = null) + public override object GetXPathValue(string culture = ".", string segment = ".") { + ContextualizeVariation(ref culture, ref segment); + lock (_locko) { var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index b5201716ac..a0ec6e4687 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -23,11 +23,12 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Constructors - public PublishedContent(ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor) + public PublishedContent(ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor) { _contentNode = contentNode; _contentData = contentData; _publishedSnapshotAccessor = publishedSnapshotAccessor; + VariationContextAccessor = variationContextAccessor; _urlName = _contentData.Name.ToUrlSegment(); IsPreviewing = _contentData.Published == false; @@ -70,6 +71,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { _contentNode = contentNode; _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; + VariationContextAccessor = origin.VariationContextAccessor; _contentData = origin._contentData; _urlName = origin._urlName; @@ -85,6 +87,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private PublishedContent(PublishedContent origin) { _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; + VariationContextAccessor = origin.VariationContextAccessor; _contentNode = origin._contentNode; _contentData = origin._contentData; @@ -308,6 +311,8 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Internal + internal IPublishedVariationContextAccessor VariationContextAccessor { get; } + // used by navigable content internal IPublishedProperty[] PropertiesArray { get; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs index 8b3d01f6e9..63ef1ae5aa 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs @@ -15,13 +15,13 @@ namespace Umbraco.Web.PublishedCache.NuCache { private readonly IMember _member; - private PublishedMember(IMember member, ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor) - : base(contentNode, contentData, publishedSnapshotAccessor) + private PublishedMember(IMember member, ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor) + : base(contentNode, contentData, publishedSnapshotAccessor, variationContextAccessor) { _member = member; } - public static IPublishedContent Create(IMember member, PublishedContentType contentType, bool previewing, IPublishedSnapshotAccessor publishedSnapshotAccessor) + public static IPublishedContent Create(IMember member, PublishedContentType contentType, bool previewing, IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor) { var d = new ContentData { @@ -37,7 +37,7 @@ namespace Umbraco.Web.PublishedCache.NuCache member.Level, member.Path, member.SortOrder, member.ParentId, member.CreateDate, member.CreatorId); - return new PublishedMember(member, n, d, publishedSnapshotAccessor).CreateModel(); + return new PublishedMember(member, n, d, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); } private static Dictionary GetPropertyValues(PublishedContentType contentType, IMember member) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index b498a1a42b..1ca5770060 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -28,7 +28,6 @@ using Umbraco.Web.Install; using Umbraco.Web.PublishedCache.NuCache.DataSource; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; -using Database = Umbraco.Web.PublishedCache.NuCache.DataSource.Database; namespace Umbraco.Web.PublishedCache.NuCache { @@ -37,7 +36,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly ServiceContext _serviceContext; private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; private readonly IScopeProvider _scopeProvider; - private readonly Database _dataSource; + private readonly IDataSource _dataSource; private readonly ILogger _logger; private readonly IDocumentRepository _documentRepository; private readonly IMediaRepository _mediaRepository; @@ -82,18 +81,19 @@ namespace Umbraco.Web.PublishedCache.NuCache public PublishedSnapshotService(Options options, MainDom mainDom, IRuntimeState runtime, ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IdkMap idkMap, - IPublishedSnapshotAccessor publishedSnapshotAccessor, ILogger logger, IScopeProvider scopeProvider, + IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor, + ILogger logger, IScopeProvider scopeProvider, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, ISystemDefaultCultureProvider systemDefaultCultureProvider, - IGlobalSettings globalSettings, ISiteDomainHelper siteDomainHelper) - : base(publishedSnapshotAccessor) + IDataSource dataSource, IGlobalSettings globalSettings, ISiteDomainHelper siteDomainHelper) + : base(publishedSnapshotAccessor, variationContextAccessor) { //if (Interlocked.Increment(ref _singletonCheck) > 1) // throw new Exception("Singleton must be instancianted only once!"); _serviceContext = serviceContext; _publishedContentTypeFactory = publishedContentTypeFactory; - _dataSource = new Database(); + _dataSource = dataSource; _logger = logger; _scopeProvider = scopeProvider; _documentRepository = documentRepository; @@ -145,13 +145,13 @@ namespace Umbraco.Web.PublishedCache.NuCache // stores are created with a db so they can write to it, but they do not read from it, // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to // figure out whether it can read the dbs or it should populate them from sql - _contentStore = new ContentStore(publishedSnapshotAccessor, logger, _localContentDb); - _mediaStore = new ContentStore(publishedSnapshotAccessor, logger, _localMediaDb); + _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localContentDb); + _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localMediaDb); } else { - _contentStore = new ContentStore(publishedSnapshotAccessor, logger); - _mediaStore = new ContentStore(publishedSnapshotAccessor, logger); + _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger); + _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger); } _domainStore = new SnapDictionary(); @@ -1025,7 +1025,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainHelper, _globalSettings, _serviceContext.LocalizationService), MediaCache = new MediaCache(previewDefault, mediaSnap, snapshotCache, elementsCache), - MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, _serviceContext.DataTypeService, _serviceContext.LocalizationService, memberTypeCache, PublishedSnapshotAccessor), + MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, _serviceContext.DataTypeService, _serviceContext.LocalizationService, memberTypeCache, PublishedSnapshotAccessor, VariationContextAccessor), DomainCache = domainCache, SnapshotCache = snapshotCache, ElementsCache = elementsCache diff --git a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs index d8db937ca8..9fa16d184c 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs @@ -36,7 +36,7 @@ namespace Umbraco.Web.PublishedCache IsMember = propertyType.ContentType.ItemType == PublishedItemType.Member; } - public override bool HasValue(string culture = null, string segment = null) + public override bool HasValue(string culture = ".", string segment = ".") => _sourceValue != null && (!(_sourceValue is string s) || !string.IsNullOrWhiteSpace(s)); // used to cache the CacheValues of this property @@ -136,9 +136,9 @@ namespace Umbraco.Web.PublishedCache return _interValue; } - public override object GetSourceValue(string culture = null, string segment = null) => _sourceValue; + public override object GetSourceValue(string culture = ".", string segment = ".") => _sourceValue; - public override object GetValue(string culture = null, string segment = null) + public override object GetValue(string culture = ".", string segment = ".") { GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); @@ -152,7 +152,7 @@ namespace Umbraco.Web.PublishedCache } } - public override object GetXPathValue(string culture = null, string segment = null) + public override object GetXPathValue(string culture = ".", string segment = ".") { GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); diff --git a/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs index 685c129224..64dda9f20b 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs @@ -1,17 +1,20 @@ using System.Collections.Generic; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Cache; namespace Umbraco.Web.PublishedCache { abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService { - protected PublishedSnapshotServiceBase(IPublishedSnapshotAccessor publishedSnapshotAccessor) + protected PublishedSnapshotServiceBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor) { PublishedSnapshotAccessor = publishedSnapshotAccessor; + VariationContextAccessor = variationContextAccessor; } public IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; } + public IPublishedVariationContextAccessor VariationContextAccessor { get; } // note: NOT setting _publishedSnapshotAccessor.PublishedSnapshot here because it is the // responsibility of the caller to manage what the 'current' facade is diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs index 3b9a97d8cc..a0efd14ba9 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs @@ -43,7 +43,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache IScopeProvider scopeProvider, ICacheProvider requestCache, IEnumerable segmentProviders, - IPublishedSnapshotAccessor publishedSnapshotAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, ISystemDefaultCultureProvider systemDefaultCultureProvider, ILogger logger, @@ -51,7 +51,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache ISiteDomainHelper siteDomainHelper, MainDom mainDom, bool testing = false, bool enableRepositoryEvents = true) - : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, segmentProviders, publishedSnapshotAccessor, + : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, segmentProviders, + publishedSnapshotAccessor, variationContextAccessor, documentRepository, mediaRepository, memberRepository, systemDefaultCultureProvider, logger, globalSettings, siteDomainHelper, null, mainDom, testing, enableRepositoryEvents) @@ -62,7 +63,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache IPublishedContentTypeFactory publishedContentTypeFactory, IScopeProvider scopeProvider, ICacheProvider requestCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, ISystemDefaultCultureProvider systemDefaultCultureProvider, ILogger logger, @@ -71,7 +72,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache PublishedContentTypeCache contentTypeCache, MainDom mainDom, bool testing, bool enableRepositoryEvents) - : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, Enumerable.Empty(), publishedSnapshotAccessor, + : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, Enumerable.Empty(), + publishedSnapshotAccessor, variationContextAccessor, documentRepository, mediaRepository, memberRepository, systemDefaultCultureProvider, logger, globalSettings, siteDomainHelper, contentTypeCache, mainDom, testing, enableRepositoryEvents) @@ -82,7 +84,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache IScopeProvider scopeProvider, ICacheProvider requestCache, IEnumerable segmentProviders, - IPublishedSnapshotAccessor publishedSnapshotAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedVariationContextAccessor variationContextAccessor, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, ISystemDefaultCultureProvider systemDefaultCultureProvider, ILogger logger, @@ -91,7 +93,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache PublishedContentTypeCache contentTypeCache, MainDom mainDom, bool testing, bool enableRepositoryEvents) - : base(publishedSnapshotAccessor) + : base(publishedSnapshotAccessor, variationContextAccessor) { _routesCache = new RoutesCache(); _publishedContentTypeFactory = publishedContentTypeFactory; diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs index c9794eb99a..aea2fc60c6 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs @@ -29,6 +29,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache factory.GetInstance().RequestCache, factory.GetInstance(), factory.GetInstance(), + factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs index ea8ab925c6..57b81b8c73 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs @@ -27,13 +27,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// /// Gets the raw value of the property. /// - public override object GetSourceValue(string culture = null, string segment = null) => _sourceValue; + public override object GetSourceValue(string culture = ".", string segment = ".") => _sourceValue; // in the Xml cache, everything is a string, and to have a value // you want to have a non-null, non-empty string. - public override bool HasValue(string culture = null, string segment = null) => _sourceValue.Trim().Length > 0; + public override bool HasValue(string culture = ".", string segment = ".") => _sourceValue.Trim().Length > 0; - public override object GetValue(string culture = null, string segment = null) + public override object GetValue(string culture = ".", string segment = ".") { // NOT caching the source (intermediate) value since we'll never need it // everything in Xml cache is per-request anyways diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index e4a3a85d13..4ae03bfc22 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -122,6 +122,8 @@ namespace Umbraco.Web #endregion #region Value + + // fixme missing variations, but recurse/variations/fallback = ? /// /// Recursively gets the value of a content's property identified by its alias. @@ -148,6 +150,8 @@ namespace Umbraco.Web /// /// The content. /// The property alias. + /// The variation language. + /// The variation segment. /// A value indicating whether to recurse. /// The default value. /// The value of the content's property identified by the alias, if it exists, otherwise a default value. @@ -158,22 +162,26 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static object Value(this IPublishedContent content, string alias, bool recurse, object defaultValue) - { + public static object Value(this IPublishedContent content, string alias, string culture = ".", string segment = ".", object defaultValue = default, bool recurse = false) + { + // fixme - variations+recurse not implemented here var property = content.GetProperty(alias, recurse); - return property == null || property.HasValue() == false ? defaultValue : property.GetValue(); + return property == null || property.HasValue(culture, segment) == false ? defaultValue : property.GetValue(); } #endregion #region Value - + /// /// Recursively gets the value of a content's property identified by its alias, converted to a specified type. /// /// The target property type. /// The content. /// The property alias. + /// The variation language. + /// The variation segment. + /// The default value. /// A value indicating whether to recurse. /// The value of the content's property identified by the alias, converted to the specified type. /// @@ -183,38 +191,13 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static T Value(this IPublishedContent content, string alias, bool recurse) - { - return content.Value(alias, recurse, false, default(T)); - } - - /// - /// Recursively gets the value of a content's property identified by its alias, converted to a specified type, if it exists, otherwise a default value. - /// - /// The target property type. - /// The content. - /// The property alias. - /// A value indicating whether to recurse. - /// The default value. - /// The value of the content's property identified by the alias, converted to the specified type, if it exists, otherwise a default value. - /// - /// Recursively means: walking up the tree from , get the first value that can be found. - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns . - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static T Value(this IPublishedContent content, string alias, bool recurse, T defaultValue) - { - return content.Value(alias, recurse, true, defaultValue); - } - - internal static T Value(this IPublishedContent content, string alias, bool recurse, bool withDefaultValue, T defaultValue) + public static T Value(this IPublishedContent content, string alias, string culture = ".", string segment = ".", T defaultValue = default, bool recurse = false) { + // fixme - variations+recurse not implemented here var property = content.GetProperty(alias, recurse); if (property == null) return defaultValue; - return property.Value(withDefaultValue, defaultValue); + return property.Value(culture, segment, defaultValue); } #endregion diff --git a/src/Umbraco.Web/PublishedContentPropertyExtension.cs b/src/Umbraco.Web/PublishedContentPropertyExtension.cs index 6d397ffaa3..e460445c6c 100644 --- a/src/Umbraco.Web/PublishedContentPropertyExtension.cs +++ b/src/Umbraco.Web/PublishedContentPropertyExtension.cs @@ -10,21 +10,16 @@ namespace Umbraco.Web { #region Value - public static T Value(this IPublishedProperty property, string culture = null, string segment = null) - { - return property.Value(false, default(T), culture, segment); - } - - public static T Value(this IPublishedProperty property, T defaultValue, string culture = null, string segment = null) - { - return property.Value(true, defaultValue, culture, segment); - } - - internal static T Value(this IPublishedProperty property, bool withDefaultValue, T defaultValue, string culture = null, string segment = null) - { - if (property.HasValue(culture, segment) == false && withDefaultValue) return defaultValue; - - // else we use .Value so we give the converter a chance to handle the default value differently + public static T Value(this IPublishedProperty property, string culture = ".", string segment = ".", T defaultValue = default) + { + // for Value when defaultValue is not specified, and HasValue() is false, we still want to convert the result (see below) + // but we have no way to tell whether default value is specified or not - we could do it with overloads, but then defaultValue + // comes right after property and conflicts with culture when T is string - so we're just not doing it - if defaultValue is + // default, whether specified or not, we give a chance to the converter + // + //if (!property.HasValue(culture, segment) && 'defaultValue is explicitely specified') return defaultValue; + + // give the converter a chance to handle the default value differently // eg for IEnumerable it may return Enumerable.Empty instead of null var value = property.GetValue(culture, segment); @@ -34,7 +29,7 @@ namespace Umbraco.Web // failed attempt. So, no need to care for value being null here. // if already the requested type, return - if (value is T) return (T)value; + if (value is T variable) return variable; // if can convert to requested type, return var convert = value.TryConvertTo(); diff --git a/src/Umbraco.Web/PublishedElementExtensions.cs b/src/Umbraco.Web/PublishedElementExtensions.cs index bde74c0ab1..57e71573b5 100644 --- a/src/Umbraco.Web/PublishedElementExtensions.cs +++ b/src/Umbraco.Web/PublishedElementExtensions.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Web; using Umbraco.Core.Models.PublishedContent; @@ -48,12 +50,13 @@ namespace Umbraco.Web /// Gets a value indicating whether the content has a value for a property identified by its alias. /// /// Returns true if GetProperty(alias) is not null and GetProperty(alias).HasValue is true. - public static bool HasValue(this IPublishedElement content, string alias, string culture = null, string segment = null) + public static bool HasValue(this IPublishedElement content, string alias, string culture = ".", string segment = ".") { var prop = content.GetProperty(alias); return prop != null && prop.HasValue(culture, segment); } + // fixme - that one is missing variations /// /// Returns one of two strings depending on whether the content has a value for a property identified by its alias. /// @@ -63,7 +66,7 @@ namespace Umbraco.Web /// The value to return if the content has no value for the property. /// Either or depending on whether the content /// has a value for the property identified by the alias. - public static IHtmlString IfHasValue(this IPublishedElement content, string alias, string valueIfTrue, string valueIfFalse = null) + public static IHtmlString IfValue(this IPublishedElement content, string alias, string valueIfTrue, string valueIfFalse = null) { return content.HasValue(alias) ? new HtmlString(valueIfTrue) @@ -81,27 +84,7 @@ namespace Umbraco.Web /// The property alias. /// The variation language. /// The variation segment. - /// The value of the content's property identified by the alias. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, returns null. - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static object Value(this IPublishedElement content, string alias, string culture = null, string segment = null) - { - var property = content.GetProperty(alias); - return property?.GetValue(culture, segment); - } - - /// - /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. - /// - /// The content. - /// The property alias. /// The default value. - /// The variation language. - /// The variation segment. /// The value of the content's property identified by the alias, if it exists, otherwise a default value. /// /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. @@ -109,31 +92,13 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static object Value(this IPublishedElement content, string alias, string defaultValue, string culture = null, string segment = null) // fixme - kill + public static object Value(this IPublishedElement content, string alias, string culture = ".", string segment = ".", object defaultValue = default) { var property = content.GetProperty(alias); - return property == null || property.HasValue(culture, segment) == false ? defaultValue : property.GetValue(culture, segment); - } + if (property == null || !property.HasValue(culture, segment)) return defaultValue; - /// - /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. - /// - /// The content. - /// The property alias. - /// The default value. - /// The variation language. - /// The variation segment. - /// The value of the content's property identified by the alias, if it exists, otherwise a default value. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, returns . - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static object Value(this IPublishedElement content, string alias, object defaultValue, string culture = null, string segment = null) - { - var property = content.GetProperty(alias); - return property == null || property.HasValue(culture, segment) == false ? defaultValue : property.GetValue(culture, segment); + // note: supporting the "." notation for 'current' is the responsibility of the IPublishedProperty + return property.GetValue(culture, segment); // tested HasValue() right above } #endregion @@ -148,6 +113,7 @@ namespace Umbraco.Web /// The property alias. /// The variation language. /// The variation segment. + /// The default value. /// The value of the content's property identified by the alias, converted to the specified type. /// /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. @@ -155,44 +121,20 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static T Value(this IPublishedElement content, string alias, string culture = null, string segment = null) - { - return content.Value(alias, false, default(T), culture, segment); - } - - /// - /// Gets the value of a content's property identified by its alias, converted to a specified type, if it exists, otherwise a default value. - /// - /// The target property type. - /// The content. - /// The property alias. - /// The default value. - /// The variation language. - /// The variation segment. - /// The value of the content's property identified by the alias, converted to the specified type, if it exists, otherwise a default value. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns . - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static T Value(this IPublishedElement content, string alias, T defaultValue, string culture = null, string segment = null) - { - return content.Value(alias, true, defaultValue, culture, segment); - } - - internal static T Value(this IPublishedElement content, string alias, bool withDefaultValue, T defaultValue, string culture = null, string segment = null) // fixme uh? + public static T Value(this IPublishedElement content, string alias, string culture = ".", string segment = ".", T defaultValue = default) { var property = content.GetProperty(alias); if (property == null) return defaultValue; - return property.Value(withDefaultValue, defaultValue, culture, segment); + // note: supporting the "." notation for 'current' is the responsibility of the IPublishedProperty + return property.Value(culture, segment, defaultValue); } #endregion #region Value or Umbraco.Field - WORK IN PROGRESS + // fixme - more work-in-progress for element.Value() and element.Value() here // trying to reproduce Umbraco.Field so we can get rid of it // // what we want: @@ -211,6 +153,18 @@ namespace Umbraco.Web // TODO: strongly typed properties howto? // there is no strongly typed recurse, etc => needs to be in ModelsBuilder? + // todo - that one can only happen in ModelsBuilder as that's where the attributes are defined + // the attribute that carries the alias is in ModelsBuilder! + //public static TValue Value(this TModel content, Expression> propertySelector, ...) + // where TModel : IPublishedElement + //{ + // PropertyInfo pi = GetPropertyFromExpression(propertySelector); + // var attr = pi.GetCustomAttribute(); + // var alias = attr.Alias; + // return content.Value(alias, ...) + //} + + // todo - that one should be refactored, missing culture and so many things public static IHtmlString Value(this IPublishedElement content, string aliases, Func format, string alt = "") { if (format == null) format = x => x.ToString(); @@ -225,7 +179,7 @@ namespace Umbraco.Web : new HtmlString(alt); } - // fixme - move that one! + // todo - that one should move to PublishedContentExtensions public static IHtmlString Value(this IPublishedContent content, string aliases, Func format, string alt = "", bool recurse = false) { if (format == null) format = x => x.ToString(); diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs index 35e5ab1af4..9437a8147d 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Web/Routing/PublishedRouter.cs @@ -533,7 +533,7 @@ namespace Umbraco.Web.Routing var redirect = false; var valid = false; IPublishedContent internalRedirectNode = null; - var internalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId, -1); + var internalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); if (internalRedirectId > 0) { @@ -739,7 +739,7 @@ namespace Umbraco.Web.Routing if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) return; - var redirectId = request.PublishedContent.Value(Constants.Conventions.Content.Redirect, -1); + var redirectId = request.PublishedContent.Value(Constants.Conventions.Content.Redirect, defaultValue: -1); var redirectUrl = "#"; if (redirectId > 0) { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a39495430b..2760229030 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -350,6 +350,7 @@ + @@ -371,7 +372,7 @@ - + diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index 89ac52949f..61f263f02c 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -374,17 +374,17 @@ namespace umbraco _content = content; } - public override bool HasValue(string culture = null, string segment = null) + public override bool HasValue(string culture = ".", string segment = ".") { return _sourceValue != null && ((_sourceValue is string) == false || string.IsNullOrWhiteSpace((string)_sourceValue) == false); } - public override object GetSourceValue(string culture = null, string segment = null) + public override object GetSourceValue(string culture = ".", string segment = ".") { return _sourceValue; } - public override object GetValue(string culture = null, string segment = null) + public override object GetValue(string culture = ".", string segment = ".") { // isPreviewing is true here since we want to preview anyway... const bool isPreviewing = true; @@ -392,7 +392,7 @@ namespace umbraco return PropertyType.ConvertInterToObject(_content, PropertyCacheLevel.Unknown, source, isPreviewing); } - public override object GetXPathValue(string culture = null, string segment = null) + public override object GetXPathValue(string culture = ".", string segment = ".") { throw new NotImplementedException(); }