Merge remote-tracking branch 'origin/release/12.0' into v12/dev

This commit is contained in:
Bjarke Berg
2023-06-27 09:18:08 +02:00
19 changed files with 130 additions and 18 deletions

View File

@@ -58,7 +58,8 @@ public class ByRouteContentApiController : ContentApiItemControllerBase
path = WebUtility.UrlDecode(path);
}
path = path.EnsureStartsWith("/");
path = path.TrimStart("/");
path = path.Length == 0 ? "/" : path;
IPublishedContent? contentItem = GetContent(path);
if (contentItem is not null)

View File

@@ -10,6 +10,6 @@ public sealed class ApiContentBuilder : ApiContentBuilderBase<IApiContent>, IApi
{
}
protected override IApiContent Create(IPublishedContent content, Guid id, string name, string contentType, IApiContentRoute route, IDictionary<string, object?> properties)
=> new ApiContent(id, name, contentType, route, properties);
protected override IApiContent Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary<string, object?> properties)
=> new ApiContent(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties);
}

View File

@@ -17,7 +17,7 @@ public abstract class ApiContentBuilderBase<T>
_outputExpansionStrategyAccessor = outputExpansionStrategyAccessor;
}
protected abstract T Create(IPublishedContent content, Guid id, string name, string contentType, IApiContentRoute route, IDictionary<string, object?> properties);
protected abstract T Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary<string, object?> properties);
public virtual T? Build(IPublishedContent content)
{
@@ -34,9 +34,7 @@ public abstract class ApiContentBuilderBase<T>
return Create(
content,
content.Key,
_apiContentNameProvider.GetName(content),
content.ContentType.Alias,
route,
properties);
}

View File

@@ -13,7 +13,7 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase<IApiConten
: base(apiContentNameProvider, apiContentRouteBuilder, outputExpansionStrategyAccessor)
=> _apiContentRouteBuilder = apiContentRouteBuilder;
protected override IApiContentResponse Create(IPublishedContent content, Guid id, string name, string contentType, IApiContentRoute route, IDictionary<string, object?> properties)
protected override IApiContentResponse Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary<string, object?> properties)
{
var routesByCulture = new Dictionary<string, IApiContentRoute>();
@@ -35,6 +35,6 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase<IApiConten
routesByCulture[publishedCultureInfo.Culture] = cultureRoute;
}
return new ApiContentResponse(id, name, contentType, route, properties, routesByCulture);
return new ApiContentResponse(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties, routesByCulture);
}
}

View File

@@ -2,14 +2,20 @@ namespace Umbraco.Cms.Core.Models.DeliveryApi;
public class ApiContent : ApiElement, IApiContent
{
public ApiContent(Guid id, string name, string contentType, IApiContentRoute route, IDictionary<string, object?> properties)
public ApiContent(Guid id, string name, string contentType, DateTime createDate, DateTime updateDate, IApiContentRoute route, IDictionary<string, object?> properties)
: base(id, contentType, properties)
{
Name = name;
CreateDate = createDate;
UpdateDate = updateDate;
Route = route;
}
public string Name { get; }
public DateTime CreateDate { get; }
public DateTime UpdateDate { get; }
public IApiContentRoute Route { get; }
}

View File

@@ -4,8 +4,8 @@ namespace Umbraco.Cms.Core.Models.DeliveryApi;
public class ApiContentResponse : ApiContent, IApiContentResponse
{
public ApiContentResponse(Guid id, string name, string contentType, IApiContentRoute route, IDictionary<string, object?> properties, IDictionary<string, IApiContentRoute> cultures)
: base(id, name, contentType, route, properties)
public ApiContentResponse(Guid id, string name, string contentType, DateTime createDate, DateTime updateDate, IApiContentRoute route, IDictionary<string, object?> properties, IDictionary<string, IApiContentRoute> cultures)
: base(id, name, contentType, createDate, updateDate, route, properties)
=> Cultures = cultures;
// a little DX; by default this dictionary will be serialized as the first part of the response due to the inner workings of the serializer.

View File

@@ -4,5 +4,9 @@ public interface IApiContent : IApiElement
{
string? Name { get; }
public DateTime CreateDate { get; }
public DateTime UpdateDate { get; }
IApiContentRoute Route { get; }
}

View File

@@ -41,6 +41,7 @@ public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex
if (namedOptions.Validator is IContentValueSetValidator contentValueSetValidator)
{
PublishedValuesOnly = contentValueSetValidator.PublishedValuesOnly;
SupportProtectedContent = contentValueSetValidator.SupportProtectedContent;
}
}

View File

@@ -47,6 +47,8 @@ public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndex, IIndexDi
public bool PublishedValuesOnly { get; protected set; } = false;
public bool SupportProtectedContent { get; protected set; } = true;
/// <summary>
/// override to check if we can actually initialize.
/// </summary>

View File

@@ -85,4 +85,18 @@
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Infrastructure.Search.IUmbracoIndexingHandler.RemoveProtectedContent</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Umbraco.Cms.Infrastructure.Examine.IUmbracoIndex.SupportProtectedContent</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>

View File

@@ -59,6 +59,7 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddSingleton<ExamineIndexRebuilder>();
builder.AddNotificationHandler<ContentCacheRefresherNotification, ContentIndexingNotificationHandler>();
builder.AddNotificationHandler<PublicAccessCacheRefresherNotification, ContentIndexingNotificationHandler>();
builder.AddNotificationHandler<ContentTypeCacheRefresherNotification, ContentTypeIndexingNotificationHandler>();
builder.AddNotificationHandler<ContentCacheRefresherNotification, DeliveryApiContentIndexingNotificationHandler>();
builder.AddNotificationHandler<ContentTypeCacheRefresherNotification, DeliveryApiContentIndexingNotificationHandler>();

View File

@@ -4,6 +4,7 @@ using Examine.Search;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.HostedServices;
using Umbraco.Cms.Infrastructure.Search;
using Umbraco.Extensions;
@@ -25,6 +26,7 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler
private readonly IPublishedContentValueSetBuilder _publishedContentValueSetBuilder;
private readonly ICoreScopeProvider _scopeProvider;
private readonly ExamineIndexingMainDomHandler _mainDomHandler;
private readonly IPublicAccessService _publicAccessService;
public ExamineUmbracoIndexingHandler(
ILogger<ExamineUmbracoIndexingHandler> logger,
@@ -35,7 +37,8 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler
IPublishedContentValueSetBuilder publishedContentValueSetBuilder,
IValueSetBuilder<IMedia> mediaValueSetBuilder,
IValueSetBuilder<IMember> memberValueSetBuilder,
ExamineIndexingMainDomHandler mainDomHandler)
ExamineIndexingMainDomHandler mainDomHandler,
IPublicAccessService publicAccessService)
{
_logger = logger;
_scopeProvider = scopeProvider;
@@ -46,6 +49,7 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler
_mediaValueSetBuilder = mediaValueSetBuilder;
_memberValueSetBuilder = memberValueSetBuilder;
_mainDomHandler = mainDomHandler;
_publicAccessService = publicAccessService;
_enabled = new Lazy<bool>(IsEnabled);
}
@@ -122,6 +126,20 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler
}
}
/// <inheritdoc />
public void RemoveProtectedContent()
{
var actions = DeferredActions.Get(_scopeProvider);
if (actions != null)
{
actions.Add(new DeferredRemoveProtectedContent(_backgroundTaskQueue, this, _publicAccessService));
}
else
{
DeferredRemoveProtectedContent.Execute(_backgroundTaskQueue, this, _publicAccessService);
}
}
/// <inheritdoc />
public void DeleteDocumentsForContentTypes(IReadOnlyCollection<int> removedContentTypes)
{
@@ -391,5 +409,50 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler
}
}
/// <summary>
/// Removes all protected content from applicable indexes on a background thread
/// </summary>
private class DeferredRemoveProtectedContent : IDeferredAction
{
private readonly IBackgroundTaskQueue _backgroundTaskQueue;
private readonly ExamineUmbracoIndexingHandler _examineUmbracoIndexingHandler;
private readonly IPublicAccessService _publicAccessService;
public DeferredRemoveProtectedContent(IBackgroundTaskQueue backgroundTaskQueue, ExamineUmbracoIndexingHandler examineUmbracoIndexingHandler, IPublicAccessService publicAccessService)
{
_backgroundTaskQueue = backgroundTaskQueue;
_examineUmbracoIndexingHandler = examineUmbracoIndexingHandler;
_publicAccessService = publicAccessService;
}
public void Execute() => Execute(_backgroundTaskQueue, _examineUmbracoIndexingHandler, _publicAccessService);
public static void Execute(IBackgroundTaskQueue backgroundTaskQueue, ExamineUmbracoIndexingHandler examineUmbracoIndexingHandler, IPublicAccessService publicAccessService)
=> backgroundTaskQueue.QueueBackgroundWorkItem(cancellationToken =>
{
using ICoreScope scope = examineUmbracoIndexingHandler._scopeProvider.CreateCoreScope(autoComplete: true);
var protectedContentIds = publicAccessService.GetAll().Select(entry => entry.ProtectedNodeId).ToArray();
if (protectedContentIds.Any() is false)
{
return Task.CompletedTask;
}
foreach (IUmbracoContentIndex index in examineUmbracoIndexingHandler._examineManager.Indexes
.OfType<IUmbracoContentIndex>()
.Where(x => x is { EnableDefaultEventHandler: true, SupportProtectedContent: false }))
{
if (cancellationToken.IsCancellationRequested)
{
return Task.CompletedTask;
}
index.DeleteFromIndex(protectedContentIds.Select(id => id.ToString()));
}
return Task.CompletedTask;
});
}
#endregion
}

View File

@@ -21,4 +21,12 @@ public interface IUmbracoIndex : IIndex, IIndexStats
/// * non-published Variants
/// </remarks>
bool PublishedValuesOnly { get; }
/// <summary>
/// Whether the index can contain protected content
/// </summary>
/// <remarks>
/// To retain backwards compatability, the default value is true
/// </remarks>
bool SupportProtectedContent { get; }
}

View File

@@ -72,7 +72,6 @@ public class MarkdownEditorValueConverter : PropertyValueConverterBase, IDeliver
return string.Empty;
}
var mark = new Markdown();
return mark.Transform(markdownString);
return markdownString;
}
}

View File

@@ -19,6 +19,11 @@ public interface IUmbracoIndexingHandler
void ReIndexForMedia(IMedia sender, bool isPublished);
/// <summary>
/// Removes any content that is flagged as protected
/// </summary>
void RemoveProtectedContent();
/// <summary>
/// Deletes all documents for the content type Ids
/// </summary>

View File

@@ -9,7 +9,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Search;
public sealed class ContentIndexingNotificationHandler : INotificationHandler<ContentCacheRefresherNotification>
public sealed class ContentIndexingNotificationHandler :
INotificationHandler<ContentCacheRefresherNotification>,
INotificationHandler<PublicAccessCacheRefresherNotification>
{
private readonly IContentService _contentService;
private readonly IUmbracoIndexingHandler _umbracoIndexingHandler;
@@ -158,4 +160,7 @@ public sealed class ContentIndexingNotificationHandler : INotificationHandler<Co
_umbracoIndexingHandler.DeleteIndexForEntities(deleteBatch, false);
}
}
public void Handle(PublicAccessCacheRefresherNotification notification)
=> _umbracoIndexingHandler.RemoveProtectedContent();
}

View File

@@ -36,6 +36,7 @@ public class PublishedContentQueryTests : ExamineBaseTest
public bool EnableDefaultEventHandler => throw new NotImplementedException();
public bool PublishedValuesOnly => throw new NotImplementedException();
public bool SupportProtectedContent => throw new NotImplementedException();
public IEnumerable<string> GetFields() => _fieldNames;
}

View File

@@ -28,6 +28,8 @@ public class ContentBuilderTests : DeliveryApiTests
var urlSegment = "url-segment";
var name = "The page";
ConfigurePublishedContentMock(content, key, name, urlSegment, contentType.Object, new[] { prop1, prop2 });
content.SetupGet(c => c.CreateDate).Returns(new DateTime(2023, 06, 01));
content.SetupGet(c => c.UpdateDate).Returns(new DateTime(2023, 07, 12));
var publishedUrlProvider = new Mock<IPublishedUrlProvider>();
publishedUrlProvider
@@ -47,6 +49,8 @@ public class ContentBuilderTests : DeliveryApiTests
Assert.AreEqual(2, result.Properties.Count);
Assert.AreEqual("Delivery API value", result.Properties["deliveryApi"]);
Assert.AreEqual("Default value", result.Properties["default"]);
Assert.AreEqual(new DateTime(2023, 06, 01), result.CreateDate);
Assert.AreEqual(new DateTime(2023, 07, 12), result.UpdateDate);
}
[Test]

View File

@@ -17,8 +17,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.DeliveryApi;
[TestFixture]
public class MarkdownEditorValueConverterTests : PropertyValueConverterTests
{
[TestCase("hello world", "<p>hello world</p>")]
[TestCase("hello *world*", "<p>hello <em>world</em></p>")]
[TestCase("hello world", "hello world")]
[TestCase("hello *world*", "hello *world*")]
[TestCase("", "")]
[TestCase(null, "")]
[TestCase(123, "")]