diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index e7dcf7cd8c..f358c0f881 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -1,41 +1,15 @@ -using Umbraco.Core.CodeAnnotations; - -namespace Umbraco.Core.Cache +namespace Umbraco.Core.Cache { /// /// Constants storing cache keys used in caching /// public static class CacheKeys { - public const string ApplicationTreeCacheKey = "ApplicationTreeCache"; - public const string ApplicationsCacheKey = "ApplicationCache"; + public const string ApplicationTreeCacheKey = "ApplicationTreeCache"; // used by ApplicationTreeService + public const string ApplicationsCacheKey = "ApplicationCache"; // used by SectionService - [UmbracoWillObsolete("This cache key is only used for the legacy 'library' caching, remove in v8")] - public const string MediaCacheKey = "UL_GetMedia"; - - [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] - public const string MacroCacheKey = "UmbracoMacroCache"; + public const string TemplateFrontEndCacheKey = "template"; // fixme usage? - public const string MacroContentCacheKey = "macroContent_"; // for macro contents - - [UmbracoWillObsolete("This cache key is only used for legacy 'library' member caching, remove in v8")] - public const string MemberLibraryCacheKey = "UL_GetMember"; - - [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] - public const string MemberBusinessLogicCacheKey = "MemberCacheItem_"; - - [UmbracoWillObsolete("This cache key is only used for legacy template business logic caching, remove in v8")] - public const string TemplateFrontEndCacheKey = "template"; - - public const string UserContextTimeoutCacheKey = "UmbracoUserContextTimeout"; - - [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] - public const string ContentTypeCacheKey = "UmbracoContentType"; - - [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] - public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:"; - - public const string IdToKeyCacheKey = "UI2K__"; - public const string KeyToIdCacheKey = "UK2I__"; + public const string MacroContentCacheKey = "macroContent_"; // used in MacroRenderers } } diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs deleted file mode 100644 index 74be6ab397..0000000000 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace Umbraco.Core.CodeAnnotations -{ - /// - /// Marks the program elements that Umbraco is experimenting with and could become public. - /// - /// - /// Indicates that Umbraco is experimenting with code that potentially could become - /// public, but we're not sure, and the code is not stable and can be refactored at any time. - /// The issue tracker should contain more details, discussion, and planning. - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] - internal sealed class UmbracoExperimentalFeatureAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a description. - /// - /// The text string that describes what is intended. - public UmbracoExperimentalFeatureAttribute(string description) - { - - } - - /// - /// Initializes a new instance of the class with a tracker url and a description. - /// - /// The url of a tracker issue containing more details, discussion, and planning. - /// The text string that describes what is intended. - public UmbracoExperimentalFeatureAttribute(string trackerUrl, string description) - { - - } - } -} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs deleted file mode 100644 index 1a65dc036e..0000000000 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace Umbraco.Core.CodeAnnotations -{ - /// - /// Marks the program elements that Umbraco is considering making public. - /// - /// - /// Indicates that Umbraco considers making the (currently internal) program element public - /// at some point in the future, but the decision is not fully made yet and is still pending reviews. - /// Note that it is not a commitment to make the program element public. It may not ever become public. - /// The issue tracker should contain more details, discussion, and planning. - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple=false, Inherited=false)] - internal sealed class UmbracoProposedPublicAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a description. - /// - /// The text string that describes what is intended. - public UmbracoProposedPublicAttribute(string description) - { - - } - - /// - /// Initializes a new instance of the class with a tracker url and a description. - /// - /// The url of a tracker issue containing more details, discussion, and planning. - /// The text string that describes what is intended. - public UmbracoProposedPublicAttribute(string trackerUrl, string description) - { - - } - } -} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs deleted file mode 100644 index 7babe71469..0000000000 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Core.CodeAnnotations -{ - /// - /// Marks the program elements that Umbraco will obsolete. - /// - /// - /// Indicates that Umbraco will obsolete the program element at some point in the future, but we do not want to - /// explicitely mark it [Obsolete] yet to avoid warning messages showing when developers compile Umbraco. - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] - internal sealed class UmbracoWillObsoleteAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a description. - /// - /// The text string that describes what is intended. - public UmbracoWillObsoleteAttribute(string description) - { - - } - - /// - /// Initializes a new instance of the class with a tracker url and a description. - /// - /// The url of a tracker issue containing more details, discussion, and planning. - /// The text string that describes what is intended. - public UmbracoWillObsoleteAttribute(string trackerUrl, string description) - { - - } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 49f679d1f4..e019f9a5d1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -194,17 +194,19 @@ namespace Umbraco.Core.Models.PublishedContent } /// - /// Determines whether a source value is an actual value, or not a value. + /// Determines whether a value is an actual value, or not a value. /// /// Used by property.HasValue and, for instance, in fallback scenarios. - public bool IsValue(object value) + public bool? IsValue(object value, PropertyValueLevel level) { - // if we have a converter, use the converter, - // otherwise use the old magic null & string comparisons + if (!_initialized) Initialize(); - return _converter == null - ? value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false) - : _converter.IsValue(value); + // if we have a converter, use the converter + if (_converter != null) + return _converter.IsValue(value, level); + + // otherwise use the old magic null & string comparisons + return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); } /// diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index 53851f8653..258febe813 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -18,9 +18,16 @@ namespace Umbraco.Core.PropertyEditors bool IsConverter(PublishedPropertyType propertyType); /// - /// Determines whether a source value is an actual value, or not a value. + /// Determines whether a value is an actual value, or not a value. /// - bool IsValue(object value); + /// + /// Called for Source, Inter and Object levels, until one does not return null. + /// Can return true (is a value), false (is not a value), or null to indicate that it + /// cannot be determined at the specified level. For instance, if source is a string that + /// could contain JSON, the decision could be made on the intermediate value. Or, if it is + /// a picker, it could be made on the object value (the actual picked object). + /// + bool? IsValue(object value, PropertyValueLevel level); /// /// Gets the type of values returned by the converter. diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs index 9a82446edd..4c20016318 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -11,8 +11,25 @@ namespace Umbraco.Core.PropertyEditors public virtual bool IsConverter(PublishedPropertyType propertyType) => false; - public bool IsValue(object value) - => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + public virtual bool? IsValue(object value, PropertyValueLevel level) + { + switch (level) + { + case PropertyValueLevel.Source: + return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + default: + throw new NotSupportedException($"Invalid level: {level}."); + } + } + + public virtual bool HasValue(IPublishedProperty property, string culture, string segment) + { + // the default implementation uses the old magic null & string comparisons, + // other implementations may be more clever, and/or test the final converted object values + // fixme - cannot access the intermediate value here? + var value = property.GetSourceValue(culture, segment); + return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + } public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) => typeof (object); diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs new file mode 100644 index 0000000000..956ce03b30 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs @@ -0,0 +1,23 @@ +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Indicates the level of a value. + /// + public enum PropertyValueLevel + { + /// + /// The source value, i.e. what is in the database. + /// + Source, + + /// + /// The conversion intermediate value. + /// + Inter, + + /// + /// The converted value. + /// + Object + } +} diff --git a/src/Umbraco.Core/StringAliasCaseType.cs b/src/Umbraco.Core/StringAliasCaseType.cs deleted file mode 100644 index 328382e1c7..0000000000 --- a/src/Umbraco.Core/StringAliasCaseType.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Umbraco.Core.CodeAnnotations; - -namespace Umbraco.Core -{ - [UmbracoWillObsolete("This enumeration should be removed. Use Umbraco.Core.Strings.CleanStringType instead.")] - public enum StringAliasCaseType - { - PascalCase, - CamelCase, - Unchanged - } -} diff --git a/src/Umbraco.Core/Strings/StringAliasCaseTypeExtensions.cs b/src/Umbraco.Core/Strings/StringAliasCaseTypeExtensions.cs deleted file mode 100644 index ce0b0f0c7a..0000000000 --- a/src/Umbraco.Core/Strings/StringAliasCaseTypeExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Umbraco.Core.Strings -{ - /// - /// Provides extension methods to StringAliasCaseType to facilitate migration to CleanStringType. - /// - public static class StringAliasCaseTypeExtensions - { - /// - /// Gets the CleanStringType value corresponding to the StringAliasCaseType value. - /// - /// The value. - /// A CleanStringType value corresponding to the StringAliasCaseType value. - public static CleanStringType ToCleanStringType(this StringAliasCaseType aliasCaseType) - { - switch (aliasCaseType) - { - case StringAliasCaseType.PascalCase: - return CleanStringType.PascalCase; - case StringAliasCaseType.CamelCase: - return CleanStringType.CamelCase; - //case StringAliasCaseType.Unchanged: - default: - return CleanStringType.Unchanged; - } - } - } -} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 49990db5de..6084580ff0 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -128,11 +128,8 @@ - - - @@ -406,6 +403,7 @@ + @@ -1439,7 +1437,6 @@ - @@ -1451,7 +1448,6 @@ - diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 39cb37f160..edc4face17 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -43,12 +43,30 @@ namespace Umbraco.Tests.Published var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); Assert.AreEqual(1234, element1.Value("prop1")); + + // 'null' would be considered a 'missing' value by the default, magic logic + var e = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", null } }, false); + Assert.IsFalse(e.HasValue("prop1")); + + // '0' would not - it's a valid integer - but the converter knows better + e = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "0" } }, false); + Assert.IsFalse(e.HasValue("prop1")); } private class SimpleConverter1 : IPropertyValueConverter { - public bool IsValue(object value) - => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + public bool? IsValue(object value, PropertyValueLevel level) + { + switch (level) + { + case PropertyValueLevel.Source: + return null; + case PropertyValueLevel.Inter: + return value is int ivalue && ivalue != 0; + default: + throw new NotSupportedException($"Invalid level: {level}."); + } + } public bool IsConverter(PublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); @@ -120,7 +138,7 @@ namespace Umbraco.Tests.Published _cacheLevel = cacheLevel; } - public bool IsValue(object value) + public bool? IsValue(object value, PropertyValueLevel level) => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); public bool IsConverter(PublishedPropertyType propertyType) diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 617429d205..33a595626e 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -210,7 +210,7 @@ namespace Umbraco.Tests.Published public int SourceConverts { get; private set; } public int InterConverts { get; private set; } - public bool IsValue(object value) + public bool? IsValue(object value, PropertyValueLevel level) => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); public bool IsConverter(PublishedPropertyType propertyType) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 21588c9ede..aa53626a78 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -761,9 +761,14 @@ function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macro * @param {string} input the string to parse */ getAnchorNames: function (input) { - var anchorPattern = //gi; + var anchors = []; + if (!input) { + return anchors; + } + + var anchorPattern = //gi; var matches = input.match(anchorPattern); - var anchors = []; + if (matches) { anchors = matches.map(function (v) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html index e4ab635a44..5dd1275014 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -34,7 +34,7 @@ diff --git a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs index 58c5732650..22f1554269 100644 --- a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs @@ -48,10 +48,6 @@ namespace Umbraco.Web.Cache runtimeCache.ClearCacheObjectTypes(); - // fixme - this is for entity service, not sure why we do it here and nowhere else? - runtimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey); - runtimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey); - var idsRemoved = new HashSet(); foreach (var payload in payloads) @@ -110,9 +106,6 @@ namespace Umbraco.Web.Cache // when a public version changes Current.ApplicationCache.ClearPartialViewCache(); MacroCacheRefresher.ClearMacroContentCache(CacheHelper); // just the content - - Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey); - Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey); } base.Refresh(payloads); diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 55ad57e330..c1b99d25ec 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -61,7 +61,6 @@ namespace Umbraco.Web.Cache foreach (var id in payloads.Select(x => x.Id)) { _idkMap.ClearCache(id); - ClearLegacyCaches(id); } if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) @@ -103,30 +102,6 @@ namespace Umbraco.Web.Cache throw new NotSupportedException(); } - private void ClearLegacyCaches(int contentTypeId /*, string contentTypeAlias, IEnumerable propertyTypeIds*/) - { - // legacy umbraco.cms.businesslogic.ContentType - - // TODO - get rid of all this mess - - // clears the cache for each property type associated with the content type - // see src/umbraco.cms/businesslogic/propertytype/propertytype.cs - // that cache is disabled because we could not clear it properly - //foreach (var pid in propertyTypeIds) - // ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.PropertyTypeCacheKey + pid); - - // clears the cache associated with the content type itself - CacheHelper.RuntimeCache.ClearCacheItem(CacheKeys.ContentTypeCacheKey + contentTypeId); - - // clears the cache associated with the content type properties collection - CacheHelper.RuntimeCache.ClearCacheItem(CacheKeys.ContentTypePropertiesCacheKey + contentTypeId); - - // clears the dictionary object cache of the legacy ContentType - // see src/umbraco.cms/businesslogic/ContentType.cs - // that cache is disabled because we could not clear it properly - //global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(contentTypeAlias); - } - #endregion #region Json diff --git a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs index 9164c64163..a4af601379 100644 --- a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs @@ -101,7 +101,6 @@ namespace Umbraco.Web.Cache { return new[] { - CacheKeys.MacroCacheKey, // umbraco.cms.businesslogic.macro.Macro objects cache CacheKeys.MacroContentCacheKey, // macro render cache }; } diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs index eb53980422..7c64f3378b 100644 --- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs @@ -56,34 +56,6 @@ namespace Umbraco.Web.Cache if (payload.ChangeTypes == TreeChangeTypes.Remove) _idkMap.ClearCache(payload.Id); - // note: ClearCacheByKeySearch - does StartsWith(...) - - // legacy alert! - // - // library cache library.GetMedia(int mediaId, bool deep) maintains a cache - // of media xml - and of *deep* media xml - using the key - // MediaCacheKey + "_" + mediaId + "_" + deep - // - // this clears the non-deep xml for the current media - // - Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - $"{CacheKeys.MediaCacheKey}_{payload.Id}_False"); - - // and then, for the entire path, we have to clear whatever might contain the media - // bearing in mind there are probably nasty race conditions here - this is all legacy - var k = $"{CacheKeys.MediaCacheKey}_{payload.Id}_"; - var x = Current.ApplicationCache.RuntimeCache.GetCacheItem(k) - as Tuple; - if (x == null) continue; - var path = x.Item2; - - foreach (var pathId in path.Split(',').Skip(1).Select(int.Parse)) - { - // this clears the deep xml for the medias in the path (skipping -1) - Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - $"{CacheKeys.MediaCacheKey}_{pathId}_True"); - } - // repository cache // it *was* done for each pathId but really that does not make sense // only need to do it for the current media diff --git a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs index a1b896dbb4..237daa39b4 100644 --- a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs @@ -60,9 +60,6 @@ namespace Umbraco.Web.Cache _idkMap.ClearCache(id); CacheHelper.ClearPartialViewCache(); - CacheHelper.RuntimeCache.ClearCacheByKeySearch($"{CacheKeys.MemberLibraryCacheKey}_{id}"); - CacheHelper.RuntimeCache.ClearCacheByKeySearch($"{CacheKeys.MemberBusinessLogicCacheKey}{id}"); - var memberCache = CacheHelper.IsolatedRuntimeCache.GetCache(); if (memberCache) memberCache.Result.ClearCacheItem(RepositoryCacheKeys.GetKey(id)); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs index 73a159cb19..8dd3bb8dc7 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -87,10 +87,34 @@ namespace Umbraco.Web.PublishedCache.NuCache _variations = origin._variations; } + // determines whether a property has value public override bool HasValue(string culture = null, string segment = null) { ContextualizeVariation(ref culture, ref segment); - return PropertyType.IsValue(GetSourceValue(culture, segment)); + + var value = GetSourceValue(culture, segment); + var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); + if (hasValue.HasValue) return hasValue.Value; + + lock (_locko) + { + value = GetInterValue(culture, segment); + hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); + if (hasValue.HasValue) return hasValue.Value; + + var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); + + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + if (!cacheValues.ObjectInitialized) + { + cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, value, _isPreviewing); + cacheValues.ObjectInitialized = true; + } + value = cacheValues.ObjectValue; + return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; + } } // used to cache the CacheValues of this property diff --git a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs index d8db937ca8..cff1e40b69 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs @@ -37,7 +37,28 @@ namespace Umbraco.Web.PublishedCache } public override bool HasValue(string culture = null, string segment = null) - => _sourceValue != null && (!(_sourceValue is string s) || !string.IsNullOrWhiteSpace(s)); + { + var hasValue = PropertyType.IsValue(_sourceValue, PropertyValueLevel.Source); + if (hasValue.HasValue) return hasValue.Value; + + GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); + + lock (_locko) + { + var value = GetInterValue(); + hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); + if (hasValue.HasValue) return hasValue.Value; + + var cacheValues = GetCacheValues(cacheLevel); + if (!cacheValues.ObjectInitialized) + { + cacheValues.ObjectValue = PropertyType.ConvertInterToObject(Element, referenceCacheLevel, value, IsPreviewing); + cacheValues.ObjectInitialized = true; + } + value = cacheValues.ObjectValue; + return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; + } + } // used to cache the CacheValues of this property // ReSharper disable InconsistentlySynchronizedField