Merge pull request #17779 from umbraco/v15/hotfix/fix-friendly-content-extension-performance

* Refactor .Children to use PublishStatusQuery

* Fix descendants

* Fix ancestors

* Fix siblings

* Handle empty string in published status service

* Fix unit test

* Fixes issue found in tests

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
Bjarke Berg
2024-12-11 19:18:49 +01:00
committed by GitHub
13 changed files with 2410 additions and 258 deletions

View File

@@ -1,5 +1,7 @@
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
@@ -16,6 +18,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
private readonly IRequestPreviewService _requestPreviewService;
private readonly IPublishedContentCache _contentCache;
private readonly IDocumentNavigationQueryService _navigationQueryService;
private readonly IPublishStatusQueryService _publishStatusQueryService;
private RequestHandlerSettings _requestSettings;
public ApiContentRouteBuilder(
@@ -25,18 +28,41 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
IRequestPreviewService requestPreviewService,
IOptionsMonitor<RequestHandlerSettings> requestSettings,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService navigationQueryService)
IDocumentNavigationQueryService navigationQueryService,
IPublishStatusQueryService publishStatusQueryService)
{
_apiContentPathProvider = apiContentPathProvider;
_variationContextAccessor = variationContextAccessor;
_requestPreviewService = requestPreviewService;
_contentCache = contentCache;
_navigationQueryService = navigationQueryService;
_publishStatusQueryService = publishStatusQueryService;
_globalSettings = globalSettings.Value;
_requestSettings = requestSettings.CurrentValue;
requestSettings.OnChange(settings => _requestSettings = settings);
}
[Obsolete("Use constructor that takes an IPublishStatusQueryService instead, scheduled for removal in v17")]
public ApiContentRouteBuilder(
IApiContentPathProvider apiContentPathProvider,
IOptions<GlobalSettings> globalSettings,
IVariationContextAccessor variationContextAccessor,
IRequestPreviewService requestPreviewService,
IOptionsMonitor<RequestHandlerSettings> requestSettings,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService navigationQueryService)
: this(
apiContentPathProvider,
globalSettings,
variationContextAccessor,
requestPreviewService,
requestSettings,
contentCache,
navigationQueryService,
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
{
}
public IApiContentRoute? Build(IPublishedContent content, string? culture = null)
{
if (content.ItemType != PublishedItemType.Content)
@@ -105,7 +131,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
{
if (isPreview is false)
{
return content.Root(_contentCache, _navigationQueryService);
return content.Root(_variationContextAccessor, _contentCache, _navigationQueryService, _publishStatusQueryService);
}
_navigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
@@ -114,6 +140,6 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
// in very edge case scenarios during preview, content.Root() does not map to the root.
// we'll code our way around it for the time being.
return rootContent.FirstOrDefault(root => root.IsAncestorOrSelf(content))
?? content.Root(_contentCache, _navigationQueryService);
?? content.Root(_variationContextAccessor, _contentCache, _navigationQueryService, _publishStatusQueryService);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services.Navigation;
@@ -24,6 +26,7 @@ public class ContentFinderByUrlAlias : IContentFinder
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IPublishedContentCache _contentCache;
private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
private readonly IPublishStatusQueryService _publishStatusQueryService;
private readonly IVariationContextAccessor _variationContextAccessor;
/// <summary>
@@ -35,16 +38,37 @@ public class ContentFinderByUrlAlias : IContentFinder
IVariationContextAccessor variationContextAccessor,
IUmbracoContextAccessor umbracoContextAccessor,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService documentNavigationQueryService)
IDocumentNavigationQueryService documentNavigationQueryService,
IPublishStatusQueryService publishStatusQueryService)
{
_publishedValueFallback = publishedValueFallback;
_variationContextAccessor = variationContextAccessor;
_umbracoContextAccessor = umbracoContextAccessor;
_contentCache = contentCache;
_documentNavigationQueryService = documentNavigationQueryService;
_publishStatusQueryService = publishStatusQueryService;
_logger = logger;
}
[Obsolete("Please use constructor that takes an IPublishStatusQueryService instead. Scheduled removal in v17")]
public ContentFinderByUrlAlias(
ILogger<ContentFinderByUrlAlias> logger,
IPublishedValueFallback publishedValueFallback,
IVariationContextAccessor variationContextAccessor,
IUmbracoContextAccessor umbracoContextAccessor,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService documentNavigationQueryService)
: this(
logger,
publishedValueFallback,
variationContextAccessor,
umbracoContextAccessor,
contentCache,
documentNavigationQueryService,
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
{
}
/// <summary>
/// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>.
/// </summary>
@@ -145,14 +169,14 @@ public class ContentFinderByUrlAlias : IContentFinder
if (rootNodeId > 0)
{
IPublishedContent? rootNode = cache?.GetById(rootNodeId);
return rootNode?.Descendants(_variationContextAccessor, _contentCache, _documentNavigationQueryService).FirstOrDefault(x => IsMatch(x, test1, test2));
return rootNode?.Descendants(_variationContextAccessor, _contentCache, _documentNavigationQueryService, _publishStatusQueryService).FirstOrDefault(x => IsMatch(x, test1, test2));
}
if (cache is not null)
{
foreach (IPublishedContent rootContent in cache.GetAtRoot())
{
IPublishedContent? c = rootContent.DescendantsOrSelf(_variationContextAccessor, _contentCache, _documentNavigationQueryService)
IPublishedContent? c = rootContent.DescendantsOrSelf(_variationContextAccessor, _contentCache, _documentNavigationQueryService, _publishStatusQueryService)
.FirstOrDefault(x => IsMatch(x, test1, test2));
if (c != null)
{

View File

@@ -27,6 +27,28 @@ namespace Umbraco.Cms.Core.Routing
/// <param name="variationContextAccessor">The current variation accessor.</param>
/// <param name="contentCache">The content cache.</param>
/// <param name="navigationQueryService">The query service for the in-memory navigation structure.</param>
/// <param name="publishStatusQueryService">The publish status query service, to query if a given content is published in a given culture.</param>
public UrlProvider(
IUmbracoContextAccessor umbracoContextAccessor,
IOptions<WebRoutingSettings> routingSettings,
UrlProviderCollection urlProviders,
MediaUrlProviderCollection mediaUrlProviders,
IVariationContextAccessor variationContextAccessor,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService navigationQueryService,
IPublishStatusQueryService publishStatusQueryService)
{
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_urlProviders = urlProviders;
_mediaUrlProviders = mediaUrlProviders;
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
_contentCache = contentCache;
_navigationQueryService = navigationQueryService;
_publishStatusQueryService = publishStatusQueryService;
Mode = routingSettings.Value.UrlProviderMode;
}
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
public UrlProvider(
IUmbracoContextAccessor umbracoContextAccessor,
IOptions<WebRoutingSettings> routingSettings,
@@ -35,14 +57,16 @@ namespace Umbraco.Cms.Core.Routing
IVariationContextAccessor variationContextAccessor,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService navigationQueryService)
: this(
umbracoContextAccessor,
routingSettings,
urlProviders,
mediaUrlProviders,
variationContextAccessor,
contentCache,
navigationQueryService,
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
{
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_urlProviders = urlProviders;
_mediaUrlProviders = mediaUrlProviders;
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
_contentCache = contentCache;
_navigationQueryService = navigationQueryService;
Mode = routingSettings.Value.UrlProviderMode;
}
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
@@ -59,7 +83,8 @@ namespace Umbraco.Cms.Core.Routing
mediaUrlProviders,
variationContextAccessor,
StaticServiceProvider.Instance.GetRequiredService<IPublishedContentCache>(),
StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationQueryService>())
StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationQueryService>(),
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
{
}
@@ -69,6 +94,7 @@ namespace Umbraco.Cms.Core.Routing
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IPublishedContentCache _contentCache;
private readonly IDocumentNavigationQueryService _navigationQueryService;
private readonly IPublishStatusQueryService _publishStatusQueryService;
/// <summary>
/// Gets or sets the provider URL mode.
@@ -147,7 +173,7 @@ namespace Umbraco.Cms.Core.Routing
// be nice with tests, assume things can be null, ultimately fall back to invariant
// (but only for variant content of course)
// We need to check all ancestors because urls are variant even for invariant content, if an ancestor is variant.
if (culture == null && content.AncestorsOrSelf(_contentCache, _navigationQueryService).Any(x => x.ContentType.VariesByCulture()))
if (culture == null && content.AncestorsOrSelf(_variationContextAccessor, _contentCache, _navigationQueryService, _publishStatusQueryService).Any(x => x.ContentType.VariesByCulture()))
{
culture = _variationContextAccessor?.VariationContext?.Culture ?? string.Empty;
}

View File

@@ -1,5 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
@@ -10,15 +12,31 @@ public class PublishStatusService : IPublishStatusManagementService, IPublishSta
private readonly ILogger<PublishStatusService> _logger;
private readonly IPublishStatusRepository _publishStatusRepository;
private readonly ICoreScopeProvider _coreScopeProvider;
private readonly ILanguageService _languageService;
private readonly IDictionary<Guid, ISet<string>> _publishedCultures = new Dictionary<Guid, ISet<string>>();
private string? DefaultCulture { get; set; }
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 17.")]
public PublishStatusService(
ILogger<PublishStatusService> logger,
IPublishStatusRepository publishStatusRepository,
ICoreScopeProvider coreScopeProvider)
: this(logger, publishStatusRepository, coreScopeProvider, StaticServiceProvider.Instance.GetRequiredService<ILanguageService>())
{
}
public PublishStatusService(
ILogger<PublishStatusService> logger,
IPublishStatusRepository publishStatusRepository,
ICoreScopeProvider coreScopeProvider,
ILanguageService languageService)
{
_logger = logger;
_publishStatusRepository = publishStatusRepository;
_coreScopeProvider = coreScopeProvider;
_languageService = languageService;
}
public async Task InitializeAsync(CancellationToken cancellationToken)
@@ -39,11 +57,16 @@ public class PublishStatusService : IPublishStatusManagementService, IPublishSta
}
}
DefaultCulture = await _languageService.GetDefaultIsoCodeAsync();
}
public bool IsDocumentPublished(Guid documentKey, string culture)
{
if (string.IsNullOrEmpty(culture) && DefaultCulture is not null)
{
culture = DefaultCulture;
}
if (_publishedCultures.TryGetValue(documentKey, out ISet<string>? publishedCultures))
{
return publishedCultures.Contains(culture, StringComparer.InvariantCultureIgnoreCase);

View File

@@ -67,6 +67,9 @@ public static class FriendlyPublishedContentExtensions
private static IMemberTypeService MemberTypeService { get; } =
StaticServiceProvider.Instance.GetRequiredService<IMemberTypeService>();
private static IPublishStatusQueryService PublishStatusQueryService { get; } =
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>();
private static INavigationQueryService GetNavigationQueryService(IPublishedContent content)
{
switch (content.ContentType.ItemType)
@@ -240,7 +243,7 @@ public static class FriendlyPublishedContentExtensions
/// set to 1.
/// </remarks>
public static IPublishedContent Root(this IPublishedContent content)
=> content.Root(GetPublishedCache(content), GetNavigationQueryService(content));
=> content.Root(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService);
/// <summary>
/// Gets the root content (ancestor or self at level 1) for the specified <paramref name="content" /> if it's of the
@@ -259,7 +262,7 @@ public static class FriendlyPublishedContentExtensions
/// </remarks>
public static T? Root<T>(this IPublishedContent content)
where T : class, IPublishedContent
=> content.Root<T>(GetPublishedCache(content), GetNavigationQueryService(content));
=> content.Root<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService);
/// <summary>
/// Gets the parent of the content item.
@@ -286,7 +289,7 @@ public static class FriendlyPublishedContentExtensions
/// <returns>The ancestors of the content, in down-top order.</returns>
/// <remarks>Does not consider the content itself.</remarks>
public static IEnumerable<IPublishedContent> Ancestors(this IPublishedContent content)
=> content.Ancestors(GetPublishedCache(content), GetNavigationQueryService(content));
=> content.Ancestors(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService);
/// <summary>
/// Gets the content and its ancestors.
@@ -294,7 +297,7 @@ public static class FriendlyPublishedContentExtensions
/// <param name="content">The content.</param>
/// <returns>The content and its ancestors, in down-top order.</returns>
public static IEnumerable<IPublishedContent> AncestorsOrSelf(this IPublishedContent content)
=> content.AncestorsOrSelf(GetPublishedCache(content), GetNavigationQueryService(content));
=> content.AncestorsOrSelf(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService);
/// <summary>
/// Gets the content and its ancestors, of a specified content type.
@@ -305,7 +308,7 @@ public static class FriendlyPublishedContentExtensions
/// <remarks>May or may not begin with the content itself, depending on its content type.</remarks>
public static IEnumerable<T> AncestorsOrSelf<T>(this IPublishedContent content)
where T : class, IPublishedContent
=> content.AncestorsOrSelf<T>(GetPublishedCache(content), GetNavigationQueryService(content));
=> content.AncestorsOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService);
/// <summary>
/// Gets the ancestor of the content, i.e. its parent.
@@ -324,7 +327,7 @@ public static class FriendlyPublishedContentExtensions
/// <remarks>Does not consider the content itself. May return <c>null</c>.</remarks>
public static T? Ancestor<T>(this IPublishedContent content)
where T : class, IPublishedContent
=> content.Ancestor<T>(GetPublishedCache(content), GetNavigationQueryService(content));
=> content.Ancestor<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService);
/// <summary>
/// Gets the content or its nearest ancestor, of a specified content type.
@@ -335,7 +338,7 @@ public static class FriendlyPublishedContentExtensions
/// <remarks>May or may not return the content itself depending on its content type. May return <c>null</c>.</remarks>
public static T? AncestorOrSelf<T>(this IPublishedContent content)
where T : class, IPublishedContent
=> content.AncestorOrSelf<T>(GetPublishedCache(content), GetNavigationQueryService(content));
=> content.AncestorOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService);
/// <summary>
/// Returns all DescendantsOrSelf of all content referenced
@@ -352,7 +355,7 @@ public static class FriendlyPublishedContentExtensions
/// </remarks>
public static IEnumerable<IPublishedContent> DescendantsOrSelfOfType(
this IEnumerable<IPublishedContent> parentNodes, string docTypeAlias, string? culture = null)
=> parentNodes.DescendantsOrSelfOfType(VariationContextAccessor, GetPublishedCache(parentNodes.First()), GetNavigationQueryService(parentNodes.First()), docTypeAlias, culture);
=> parentNodes.DescendantsOrSelfOfType(VariationContextAccessor, GetPublishedCache(parentNodes.First()), GetNavigationQueryService(parentNodes.First()), PublishStatusQueryService, docTypeAlias, culture);
/// <summary>
/// Returns all DescendantsOrSelf of all content referenced
@@ -370,77 +373,77 @@ public static class FriendlyPublishedContentExtensions
this IEnumerable<IPublishedContent> parentNodes,
string? culture = null)
where T : class, IPublishedContent
=> parentNodes.DescendantsOrSelf<T>(VariationContextAccessor, GetPublishedCache(parentNodes.First()), GetNavigationQueryService(parentNodes.First()), culture);
=> parentNodes.DescendantsOrSelf<T>(VariationContextAccessor, GetPublishedCache(parentNodes.First()), GetNavigationQueryService(parentNodes.First()), PublishStatusQueryService, culture);
public static IEnumerable<IPublishedContent> Descendants(this IPublishedContent content, string? culture = null)
=> content.Descendants(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.Descendants(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
public static IEnumerable<IPublishedContent> Descendants(this IPublishedContent content, int level, string? culture = null)
=> content.Descendants(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), level, culture);
=> content.Descendants(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, level, culture);
public static IEnumerable<IPublishedContent> DescendantsOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null)
=> content.DescendantsOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), contentTypeAlias, culture);
=> content.DescendantsOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, contentTypeAlias, culture);
public static IEnumerable<T> Descendants<T>(this IPublishedContent content, string? culture = null)
where T : class, IPublishedContent
=> content.Descendants<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.Descendants<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
public static IEnumerable<T> Descendants<T>(this IPublishedContent content, int level, string? culture = null)
where T : class, IPublishedContent
=> content.Descendants<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), level, culture);
=> content.Descendants<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, level, culture);
public static IEnumerable<IPublishedContent> DescendantsOrSelf(
this IPublishedContent content,
string? culture = null)
=> content.DescendantsOrSelf(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.DescendantsOrSelf(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
public static IEnumerable<IPublishedContent> DescendantsOrSelf(this IPublishedContent content, int level, string? culture = null)
=> content.DescendantsOrSelf(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), level, culture);
=> content.DescendantsOrSelf(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, level, culture);
public static IEnumerable<IPublishedContent> DescendantsOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null)
=> content.DescendantsOrSelfOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), contentTypeAlias, culture);
=> content.DescendantsOrSelfOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, contentTypeAlias, culture);
public static IEnumerable<T> DescendantsOrSelf<T>(this IPublishedContent content, string? culture = null)
where T : class, IPublishedContent
=> content.DescendantsOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.DescendantsOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
public static IEnumerable<T> DescendantsOrSelf<T>(this IPublishedContent content, int level, string? culture = null)
where T : class, IPublishedContent
=> content.DescendantsOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), level, culture);
=> content.DescendantsOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, level, culture);
public static IPublishedContent? Descendant(this IPublishedContent content, string? culture = null)
=> content.Descendant(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.Descendant(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
public static IPublishedContent? Descendant(this IPublishedContent content, int level, string? culture = null)
=> content.Descendant(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), level, culture);
=> content.Descendant(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, level, culture);
public static IPublishedContent? DescendantOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null)
=> content.DescendantOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), contentTypeAlias, culture);
=> content.DescendantOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, contentTypeAlias, culture);
public static T? Descendant<T>(this IPublishedContent content, string? culture = null)
where T : class, IPublishedContent
=> content.Descendant<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.Descendant<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
public static T? Descendant<T>(this IPublishedContent content, int level, string? culture = null)
where T : class, IPublishedContent
=> content.Descendant<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), level, culture);
=> content.Descendant<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, level, culture);
public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string? culture = null)
=> content.DescendantOrSelf(VariationContextAccessor, culture);
=> content.DescendantOrSelf(VariationContextAccessor, PublishStatusQueryService, culture);
public static IPublishedContent? DescendantOrSelf(this IPublishedContent content, int level, string? culture = null)
=> content.DescendantOrSelf(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), level, culture);
=> content.DescendantOrSelf(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, level, culture);
public static IPublishedContent? DescendantOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null)
=> content.DescendantOrSelfOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), contentTypeAlias, culture);
=> content.DescendantOrSelfOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, contentTypeAlias, culture);
public static T? DescendantOrSelf<T>(this IPublishedContent content, string? culture = null)
where T : class, IPublishedContent
=> content.DescendantOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.DescendantOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
public static T? DescendantOrSelf<T>(this IPublishedContent content, int level, string? culture = null)
where T : class, IPublishedContent
=> content.DescendantOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), level, culture);
=> content.DescendantOrSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, level, culture);
/// <summary>
/// Gets the children of the content item.
@@ -468,7 +471,7 @@ public static class FriendlyPublishedContentExtensions
/// </para>
/// </remarks>
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, string? culture = null)
=> content.Children(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.Children(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
/// <summary>
/// Gets the children of the content, filtered by a predicate.
@@ -487,7 +490,7 @@ public static class FriendlyPublishedContentExtensions
this IPublishedContent content,
Func<IPublishedContent, bool> predicate,
string? culture = null)
=> content.Children(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), predicate, culture);
=> content.Children(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, predicate, culture);
/// <summary>
/// Gets the children of the content, of any of the specified types.
@@ -500,7 +503,7 @@ public static class FriendlyPublishedContentExtensions
/// <param name="contentTypeAlias">The content type alias.</param>
/// <returns>The children of the content, of any of the specified types.</returns>
public static IEnumerable<IPublishedContent>? ChildrenOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null)
=> content.ChildrenOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), contentTypeAlias, culture);
=> content.ChildrenOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, contentTypeAlias, culture);
/// <summary>
/// Gets the children of the content, of a given content type.
@@ -517,30 +520,30 @@ public static class FriendlyPublishedContentExtensions
/// </remarks>
public static IEnumerable<T>? Children<T>(this IPublishedContent content, string? culture = null)
where T : class, IPublishedContent
=> content.Children<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.Children<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
public static IPublishedContent? FirstChild(this IPublishedContent content, string? culture = null)
=> content.FirstChild(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.FirstChild(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
/// <summary>
/// Gets the first child of the content, of a given content type.
/// </summary>
public static IPublishedContent? FirstChildOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null)
=> content.FirstChildOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), contentTypeAlias, culture);
=> content.FirstChildOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, contentTypeAlias, culture);
public static IPublishedContent? FirstChild(this IPublishedContent content, Func<IPublishedContent, bool> predicate, string? culture = null)
=> content.FirstChild(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), predicate, culture);
=> content.FirstChild(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, predicate, culture);
public static IPublishedContent? FirstChild(this IPublishedContent content, Guid uniqueId, string? culture = null)
=> content.FirstChild(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), uniqueId, culture);
=> content.FirstChild(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, uniqueId, culture);
public static T? FirstChild<T>(this IPublishedContent content, string? culture = null)
where T : class, IPublishedContent
=> content.FirstChild<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.FirstChild<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
public static T? FirstChild<T>(this IPublishedContent content, Func<T, bool> predicate, string? culture = null)
where T : class, IPublishedContent
=> content.FirstChild(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), predicate, culture);
=> content.FirstChild(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, predicate, culture);
/// <summary>
/// Gets the siblings of the content.
@@ -555,7 +558,7 @@ public static class FriendlyPublishedContentExtensions
/// <para>Note that in V7 this method also return the content node self.</para>
/// </remarks>
public static IEnumerable<IPublishedContent>? Siblings(this IPublishedContent content, string? culture = null)
=> content.Siblings(GetPublishedCache(content), GetNavigationQueryService(content), VariationContextAccessor, culture);
=> content.Siblings(GetPublishedCache(content), GetNavigationQueryService(content), VariationContextAccessor, PublishStatusQueryService, culture);
/// <summary>
/// Gets the siblings of the content, of a given content type.
@@ -571,7 +574,7 @@ public static class FriendlyPublishedContentExtensions
/// <para>Note that in V7 this method also return the content node self.</para>
/// </remarks>
public static IEnumerable<IPublishedContent>? SiblingsOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null)
=> content.SiblingsOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), contentTypeAlias, culture);
=> content.SiblingsOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, contentTypeAlias, culture);
/// <summary>
/// Gets the siblings of the content, of a given content type.
@@ -588,7 +591,7 @@ public static class FriendlyPublishedContentExtensions
/// </remarks>
public static IEnumerable<T>? Siblings<T>(this IPublishedContent content, string? culture = null)
where T : class, IPublishedContent
=> content.Siblings<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.Siblings<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
/// <summary>
/// Gets the siblings of the content including the node itself to indicate the position.
@@ -602,7 +605,7 @@ public static class FriendlyPublishedContentExtensions
public static IEnumerable<IPublishedContent>? SiblingsAndSelf(
this IPublishedContent content,
string? culture = null)
=> content.SiblingsAndSelf(GetPublishedCache(content), GetNavigationQueryService(content), VariationContextAccessor, culture);
=> content.SiblingsAndSelf(GetPublishedCache(content), GetNavigationQueryService(content), VariationContextAccessor, PublishStatusQueryService, culture);
/// <summary>
/// Gets the siblings of the content including the node itself to indicate the position, of a given content type.
@@ -618,7 +621,7 @@ public static class FriendlyPublishedContentExtensions
this IPublishedContent content,
string contentTypeAlias,
string? culture = null)
=> content.SiblingsAndSelfOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), contentTypeAlias, culture);
=> content.SiblingsAndSelfOfType(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, contentTypeAlias, culture);
/// <summary>
/// Gets the siblings of the content including the node itself to indicate the position, of a given content type.
@@ -632,7 +635,7 @@ public static class FriendlyPublishedContentExtensions
/// <returns>The siblings of the content including the node itself, of the given content type.</returns>
public static IEnumerable<T>? SiblingsAndSelf<T>(this IPublishedContent content, string? culture = null)
where T : class, IPublishedContent
=> content.SiblingsAndSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), culture);
=> content.SiblingsAndSelf<T>(VariationContextAccessor, GetPublishedCache(content), GetNavigationQueryService(content), PublishStatusQueryService, culture);
/// <summary>
/// Gets the url of the content item.

View File

@@ -98,7 +98,9 @@ public class PublishStatusServiceTest : UmbracoIntegrationTestWithContent
var sut = new PublishStatusService(
GetRequiredService<ILogger<PublishStatusService>>(),
GetRequiredService<IPublishStatusRepository>(),
GetRequiredService<ICoreScopeProvider>());
GetRequiredService<ICoreScopeProvider>(),
GetRequiredService<ILanguageService>()
);
Assert.IsFalse(sut.IsDocumentPublished(Textpage.Key, DefaultCulture));
@@ -118,7 +120,8 @@ public class PublishStatusServiceTest : UmbracoIntegrationTestWithContent
var sut = new PublishStatusService(
GetRequiredService<ILogger<PublishStatusService>>(),
GetRequiredService<IPublishStatusRepository>(),
GetRequiredService<ICoreScopeProvider>());
GetRequiredService<ICoreScopeProvider>(),
GetRequiredService<ILanguageService>());
Assert.IsFalse(sut.IsDocumentPublished(Textpage.Key, DefaultCulture));

View File

@@ -11,6 +11,7 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Install;
@@ -34,7 +35,8 @@ internal class UmbracoCustomizations : ICustomization
.Customize(new ConstructorCustomization(typeof(DatabaseSchemaCreatorFactory), new GreedyConstructorQuery()))
.Customize(new ConstructorCustomization(typeof(InstallHelper), new GreedyConstructorQuery()))
.Customize(new ConstructorCustomization(typeof(DatabaseBuilder), new GreedyConstructorQuery()))
.Customize(new ConstructorCustomization(typeof(ContentVersionService), new GreedyConstructorQuery()));
.Customize(new ConstructorCustomization(typeof(ContentVersionService), new GreedyConstructorQuery()))
.Customize(new ConstructorCustomization(typeof(ContentFinderByUrlAlias), new GreedyConstructorQuery()));
// When requesting an IUserStore ensure we actually uses a IUserLockoutStore
fixture.Customize<IUserStore<BackOfficeIdentityUser>>(cc =>

View File

@@ -429,9 +429,10 @@ public class ContentRouteBuilderTests : DeliveryApiTests
{
var variantContextAccessor = Mock.Of<IVariationContextAccessor>();
string Url(IPublishedContent content, string? culture)
{
var ancestorsOrSelf = content.AncestorsOrSelf(contentCache, navigationQueryService).ToArray();
var ancestorsOrSelf = content.AncestorsOrSelf(variantContextAccessor, contentCache, navigationQueryService, PublishStatusQueryService).ToArray();
return ancestorsOrSelf.All(c => c.IsPublished(culture))
? string.Join("/", ancestorsOrSelf.Reverse().Skip(hideTopLevelNodeFromPath ? 1 : 0).Select(c => c.UrlSegment(variantContextAccessor, culture))).EnsureStartsWith("/")
: "#";

View File

@@ -20,6 +20,8 @@ public class DeliveryApiTests
protected IPublishedPropertyType DefaultPropertyType { get; private set; }
protected IPublishStatusQueryService PublishStatusQueryService { get; private set; }
[SetUp]
public virtual void Setup()
{
@@ -57,6 +59,13 @@ public class DeliveryApiTests
defaultPropertyValueConverter.Setup(p => p.GetPropertyCacheLevel(It.IsAny<IPublishedPropertyType>())).Returns(PropertyCacheLevel.None);
DefaultPropertyType = SetupPublishedPropertyType(defaultPropertyValueConverter.Object, "default", "Default.Editor");
var publishStatusQueryService = new Mock<IPublishStatusQueryService>();
publishStatusQueryService
.Setup(x => x.IsDocumentPublished(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns(true);
PublishStatusQueryService = publishStatusQueryService.Object;
}
protected IPublishedPropertyType SetupPublishedPropertyType(IPropertyValueConverter valueConverter, string propertyTypeAlias, string editorAlias, object? dataTypeConfiguration = null)
@@ -117,7 +126,8 @@ public class DeliveryApiTests
IRequestPreviewService? requestPreviewService = null,
IOptionsMonitor<RequestHandlerSettings>? requestHandlerSettingsMonitor = null,
IPublishedContentCache? contentCache = null,
IDocumentNavigationQueryService? navigationQueryService = null)
IDocumentNavigationQueryService? navigationQueryService = null,
IPublishStatusQueryService? publishStatusQueryService = null)
{
if (requestHandlerSettingsMonitor == null)
{
@@ -133,6 +143,7 @@ public class DeliveryApiTests
requestPreviewService ?? Mock.Of<IRequestPreviewService>(),
requestHandlerSettingsMonitor,
contentCache ?? Mock.Of<IPublishedContentCache>(),
navigationQueryService ?? Mock.Of<IDocumentNavigationQueryService>());
navigationQueryService ?? Mock.Of<IDocumentNavigationQueryService>(),
publishStatusQueryService ?? PublishStatusQueryService);
}
}

View File

@@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Tests.UnitTests.AutoFixture;
@@ -30,6 +31,7 @@ public class ContentFinderByUrlAliasTests
[Frozen] IUmbracoContextAccessor umbracoContextAccessor,
[Frozen] IUmbracoContext umbracoContext,
[Frozen] IVariationContextAccessor variationContextAccessor,
[Frozen] IPublishStatusQueryService publishStatusQueryService,
IFileService fileService,
ContentFinderByUrlAlias sut,
IPublishedContent[] rootContents,
@@ -48,6 +50,7 @@ public class ContentFinderByUrlAliasTests
Mock.Get(urlProperty).Setup(x => x.GetValue(null, null)).Returns(relativeUrl);
Mock.Get(variationContextAccessor).Setup(x => x.VariationContext).Returns(variationContext);
Mock.Get(publishStatusQueryService).Setup(x => x.IsDocumentPublished(It.IsAny<Guid>(), It.IsAny<string>())).Returns(true);
var publishedRequestBuilder = new PublishedRequestBuilder(new Uri(absoluteUrl, UriKind.Absolute), fileService);
// Act

View File

@@ -99,7 +99,8 @@ public class HtmlImageSourceParserTests
new MediaUrlProviderCollection(() => new[] { mediaUrlProvider.Object }),
Mock.Of<IVariationContextAccessor>(),
Mock.Of<IPublishedContentCache>(),
Mock.Of<IDocumentNavigationQueryService>());
Mock.Of<IDocumentNavigationQueryService>(),
Mock.Of<IPublishStatusQueryService>());
using (var reference = umbracoContextFactory.EnsureUmbracoContext())
{

View File

@@ -235,6 +235,11 @@ public class HtmlLocalLinkParserTests
mediaCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(media.Object);
mediaCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media.Object);
var publishStatusQueryService = new Mock<IPublishStatusQueryService>();
publishStatusQueryService
.Setup(x => x.IsDocumentPublished(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns(true);
var publishedUrlProvider = new UrlProvider(
umbracoContextAccessor,
Options.Create(webRoutingSettings),
@@ -242,7 +247,8 @@ public class HtmlLocalLinkParserTests
new MediaUrlProviderCollection(() => new[] { mediaUrlProvider.Object }),
Mock.Of<IVariationContextAccessor>(),
contentCache.Object,
navigationQueryService.Object);
navigationQueryService.Object,
publishStatusQueryService.Object);
var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);