Merge remote-tracking branch 'origin/release/12.0' into v12/dev
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -4,5 +4,9 @@ public interface IApiContent : IApiElement
|
||||
{
|
||||
string? Name { get; }
|
||||
|
||||
public DateTime CreateDate { get; }
|
||||
|
||||
public DateTime UpdateDate { get; }
|
||||
|
||||
IApiContentRoute Route { get; }
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex
|
||||
if (namedOptions.Validator is IContentValueSetValidator contentValueSetValidator)
|
||||
{
|
||||
PublishedValuesOnly = contentValueSetValidator.PublishedValuesOnly;
|
||||
SupportProtectedContent = contentValueSetValidator.SupportProtectedContent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ public class MarkdownEditorValueConverter : PropertyValueConverterBase, IDeliver
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var mark = new Markdown();
|
||||
return mark.Transform(markdownString);
|
||||
return markdownString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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, "")]
|
||||
|
||||
Reference in New Issue
Block a user