Skip to content

Commit 3c8a621

Browse files
committed
Merge branch 'v13/dev' into v13/contrib
2 parents 0f02584 + a3db456 commit 3c8a621

File tree

35 files changed

+405
-214
lines changed

35 files changed

+405
-214
lines changed

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
<PackageVersion Include="Asp.Versioning.Mvc" Version="7.1.1" />
4646
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="7.1.0" />
4747
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
48-
<PackageVersion Include="Examine" Version="3.5.0" />
49-
<PackageVersion Include="Examine.Core" Version="3.5.0" />
48+
<PackageVersion Include="Examine" Version="3.7.0" />
49+
<PackageVersion Include="Examine.Core" Version="3.7.0" />
5050
<PackageVersion Include="HtmlAgilityPack" Version="1.11.71" />
5151
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
5252
<PackageVersion Include="MailKit" Version="4.8.0" />

build/azure-pipelines.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ stages:
7171
- job: A
7272
displayName: Build Umbraco CMS
7373
pool:
74-
vmImage: 'ubuntu-latest'
74+
vmImage: 'windows-latest'
7575
steps:
7676
- checkout: self
7777
submodules: false

src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.AspNetCore.Http;
1+
using Microsoft.AspNetCore.Http;
22
using Umbraco.Cms.Core.DeliveryApi;
33

44
namespace Umbraco.Cms.Api.Delivery.Services;
@@ -11,5 +11,5 @@ public RequestPreviewService(IHttpContextAccessor httpContextAccessor)
1111
}
1212

1313
/// <inheritdoc />
14-
public bool IsPreview() => GetHeaderValue("Preview") == "true";
14+
public bool IsPreview() => string.Equals(GetHeaderValue("Preview"), "true", StringComparison.OrdinalIgnoreCase);
1515
}

src/Umbraco.Core/EmbeddedResources/Lang/da.xml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,6 @@
298298
<key alias="removeTextBox">Fjern denne tekstboks</key>
299299
<key alias="contentRoot">Indholdsrod</key>
300300
<key alias="includeUnpublished">Inkluder ikke-udgivet indhold.</key>
301-
<key alias="forceRepublish">Udgiv uændrede elementer.</key>
302-
<key alias="forceRepublishWarning">ADVARSEL: Udgivelse af alle sider under denne i indholdstræet, uanset om de er ændret eller ej, kan være en ressourcekrævende og langvarig proces.</key>
303-
<key alias="forceRepublishAdvisory">Dette bør ikke være nødvendigt under normale omstændigheder, så fortsæt kun med denne handling, hvis du er sikker på, at det er nødvendigt.</key>
304301
<key alias="isSensitiveValue">Denne værdi er skjult. Hvis du har brug for adgang til at se denne værdi, bedes du
305302
kontakte din administrator.
306303
</key>

src/Umbraco.Core/EmbeddedResources/Lang/en.xml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,6 @@
309309
<key alias="removeTextBox">Remove this text box</key>
310310
<key alias="contentRoot">Content root</key>
311311
<key alias="includeUnpublished">Include unpublished content items.</key>
312-
<key alias="forceRepublish">Publish unchanged items.</key>
313-
<key alias="forceRepublishWarning">WARNING: Publishing all pages below this one in the content tree, whether or not they have changed, can be an expensive and long-running operation.</key>
314-
<key alias="forceRepublishAdvisory">This should not be necessary in normal circumstances so please only proceed with this option selected if you are certain it is required.</key>
315312
<key alias="isSensitiveValue">This value is hidden. If you need access to view this value please contact your
316313
website administrator.
317314
</key>

src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,6 @@
308308
<key alias="removeTextBox">Remove this text box</key>
309309
<key alias="contentRoot">Content root</key>
310310
<key alias="includeUnpublished">Include unpublished content items.</key>
311-
<key alias="forceRepublish">Publish unchanged items.</key>
312-
<key alias="forceRepublishWarning">WARNING: Publishing all pages below this one in the content tree, whether or not they have changed, can be an expensive and long-running operation.</key>
313-
<key alias="forceRepublishAdvisory">This should not be necessary in normal circumstances so please only proceed with this option selected if you are certain it is required.</key>
314311
<key alias="isSensitiveValue">This value is hidden. If you need access to view this value please contact your
315312
website administrator.
316313
</key>

src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -59,43 +59,11 @@ public enum ContentSaveAction
5959
/// Saves and publishes the content item including all descendants regardless of whether they have a published version
6060
/// or not.
6161
/// </summary>
62-
[Obsolete("This option is no longer used as the 'force' aspect has been extended into options for publishing unpublished and re-publishing changed content. Please use one of those options instead.")]
6362
PublishWithDescendantsForce = 10,
6463

6564
/// <summary>
6665
/// Creates and publishes the new content item including all descendants regardless of whether they have a published
6766
/// version or not.
6867
/// </summary>
69-
[Obsolete("This option is no longer used as the 'force' aspect has been extended into options for publishing unpublished and re-publishing changed content. Please use one of those options instead.")]
7068
PublishWithDescendantsForceNew = 11,
71-
72-
/// <summary>
73-
/// Saves and publishes the content item including all descendants including publishing previously unpublished content.
74-
/// </summary>
75-
PublishWithDescendantsIncludeUnpublished = 12,
76-
77-
/// <summary>
78-
/// Saves and publishes the new content item including all descendants including publishing previously unpublished content.
79-
/// </summary>
80-
PublishWithDescendantsIncludeUnpublishedNew = 13,
81-
82-
/// <summary>
83-
/// Saves and publishes the content item including all descendants irrespective of whether there are any pending changes.
84-
/// </summary>
85-
PublishWithDescendantsForceRepublish = 14,
86-
87-
/// <summary>
88-
/// Saves and publishes the new content item including all descendants including publishing previously unpublished content.
89-
/// </summary>
90-
PublishWithDescendantsForceRepublishNew = 15,
91-
92-
/// <summary>
93-
/// Saves and publishes the content item including all descendants including publishing previously unpublished content and irrespective of whether there are any pending changes.
94-
/// </summary>
95-
PublishWithDescendantsIncludeUnpublishedAndForceRepublish = 16,
96-
97-
/// <summary>
98-
/// Saves and publishes the new content item including all descendants including publishing previously unpublished content and irrespective of whether there are any pending changes.
99-
/// </summary>
100-
PublishWithDescendantsIncludeUnpublishedAndForceRepublishNew = 17,
10169
}

src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public interface IPublishedSnapshotService : IDisposable
3131
/// </remarks>
3232
IPublishedSnapshot CreatePublishedSnapshot(string? previewToken);
3333

34+
/// <summary>
35+
/// Indicates if the database cache is in the process of being rebuilt.
36+
/// </summary>
37+
/// <returns></returns>
38+
bool IsRebuilding() => false;
39+
3440
/// <summary>
3541
/// Rebuilds internal database caches (but does not reload).
3642
/// </summary>
@@ -61,6 +67,38 @@ void Rebuild(
6167
IReadOnlyCollection<int>? mediaTypeIds = null,
6268
IReadOnlyCollection<int>? memberTypeIds = null);
6369

70+
/// <summary>
71+
/// Rebuilds internal database caches (but does not reload).
72+
/// </summary>
73+
/// <param name="contentTypeIds">
74+
/// If not null will process content for the matching content types, if empty will process all
75+
/// content
76+
/// </param>
77+
/// <param name="mediaTypeIds">
78+
/// If not null will process content for the matching media types, if empty will process all
79+
/// media
80+
/// </param>
81+
/// <param name="memberTypeIds">
82+
/// If not null will process content for the matching members types, if empty will process all
83+
/// members
84+
/// </param>
85+
/// <param name="useBackgroundThread">Flag indicating whether to use a background thread for the operation and immediately return to the caller.</param>
86+
/// <remarks>
87+
/// <para>
88+
/// Forces the snapshot service to rebuild its internal database caches. For instance, some caches
89+
/// may rely on a database table to store pre-serialized version of documents.
90+
/// </para>
91+
/// <para>
92+
/// This does *not* reload the caches. Caches need to be reloaded, for instance via
93+
/// <see cref="DistributedCache" /> RefreshAllPublishedSnapshot method.
94+
/// </para>
95+
/// </remarks>
96+
void Rebuild(
97+
bool useBackgroundThread,
98+
IReadOnlyCollection<int>? contentTypeIds = null,
99+
IReadOnlyCollection<int>? mediaTypeIds = null,
100+
IReadOnlyCollection<int>? memberTypeIds = null) => Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds);
101+
64102

65103
/// <summary>
66104
/// Rebuilds all internal database caches (but does not reload).
@@ -77,6 +115,22 @@ void Rebuild(
77115
/// </remarks>
78116
void RebuildAll() => Rebuild(Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>());
79117

118+
/// <summary>
119+
/// Rebuilds all internal database caches (but does not reload).
120+
/// </summary>
121+
/// <param name="useBackgroundThread">Flag indicating whether to use a background thread for the operation and immediately return to the caller.</param>
122+
/// <remarks>
123+
/// <para>
124+
/// Forces the snapshot service to rebuild its internal database caches. For instance, some caches
125+
/// may rely on a database table to store pre-serialized version of documents.
126+
/// </para>
127+
/// <para>
128+
/// This does *not* reload the caches. Caches need to be reloaded, for instance via
129+
/// <see cref="DistributedCache" /> RefreshAllPublishedSnapshot method.
130+
/// </para>
131+
/// </remarks>
132+
void RebuildAll(bool useBackgroundThread) => Rebuild(useBackgroundThread, Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>());
133+
80134
/* An IPublishedCachesService implementation can rely on transaction-level events to update
81135
* its internal, database-level data, as these events are purely internal. However, it cannot
82136
* rely on cache refreshers CacheUpdated events to update itself, as these events are external

src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Umbraco.Cms.Core.Services;
1616
using Umbraco.Cms.Core.Services.Changes;
1717
using Umbraco.Cms.Core.Sync;
18+
using Umbraco.Cms.Infrastructure.HostedServices;
1819
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
1920
using Umbraco.Cms.Infrastructure.PublishedCache.Persistence;
2021
using Umbraco.Extensions;
@@ -31,6 +32,9 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
3132
// means faster execution, but uses memory - not sure if we want it
3233
// so making it configurable.
3334
public static readonly bool FullCacheWhenPreviewing = true;
35+
36+
private const string IsRebuildingDatabaseCacheRuntimeCacheKey = "temp_database_cache_rebuild_op";
37+
3438
private readonly NuCacheSettings _config;
3539
private readonly ContentDataSerializer _contentDataSerializer;
3640
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
@@ -51,6 +55,8 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
5155
private readonly object _storesLock = new();
5256
private readonly ISyncBootStateAccessor _syncBootStateAccessor;
5357
private readonly IVariationContextAccessor _variationContextAccessor;
58+
private readonly IBackgroundTaskQueue _backgroundTaskQueue;
59+
private readonly IAppPolicyCache _runtimeCache;
5460

5561
private long _contentGen;
5662

@@ -91,7 +97,9 @@ public PublishedSnapshotService(
9197
IPublishedModelFactory publishedModelFactory,
9298
IHostingEnvironment hostingEnvironment,
9399
IOptions<NuCacheSettings> config,
94-
ContentDataSerializer contentDataSerializer)
100+
ContentDataSerializer contentDataSerializer,
101+
IBackgroundTaskQueue backgroundTaskQueue,
102+
AppCaches appCaches)
95103
{
96104
_options = options;
97105
_syncBootStateAccessor = syncBootStateAccessor;
@@ -111,6 +119,8 @@ public PublishedSnapshotService(
111119
_contentDataSerializer = contentDataSerializer;
112120
_config = config.Value;
113121
_publishedModelFactory = publishedModelFactory;
122+
_backgroundTaskQueue = backgroundTaskQueue;
123+
_runtimeCache = appCaches.RuntimeCache;
114124
}
115125

116126
protected PublishedSnapshot? CurrentPublishedSnapshot
@@ -349,12 +359,66 @@ public IPublishedSnapshot CreatePublishedSnapshot(string? previewToken)
349359
return new PublishedSnapshot(this, preview);
350360
}
351361

362+
/// <inheritdoc />
363+
public bool IsRebuilding() => _runtimeCache.Get(IsRebuildingDatabaseCacheRuntimeCacheKey) is not null;
364+
352365
/// <inheritdoc />
353366
public void Rebuild(
354367
IReadOnlyCollection<int>? contentTypeIds = null,
355368
IReadOnlyCollection<int>? mediaTypeIds = null,
356369
IReadOnlyCollection<int>? memberTypeIds = null)
357-
=> _publishedContentService.Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds);
370+
=> Rebuild(false, contentTypeIds, mediaTypeIds, memberTypeIds);
371+
372+
/// <inheritdoc />
373+
public void Rebuild(
374+
bool useBackgroundThread,
375+
IReadOnlyCollection<int>? contentTypeIds = null,
376+
IReadOnlyCollection<int>? mediaTypeIds = null,
377+
IReadOnlyCollection<int>? memberTypeIds = null)
378+
{
379+
if (useBackgroundThread)
380+
{
381+
_logger.LogInformation("Starting async background thread for rebuilding database cache.");
382+
383+
_backgroundTaskQueue.QueueBackgroundWorkItem(
384+
cancellationToken =>
385+
{
386+
// Do not flow AsyncLocal to the child thread
387+
using (ExecutionContext.SuppressFlow())
388+
{
389+
Task.Run(() => PerformRebuild(contentTypeIds, mediaTypeIds, memberTypeIds));
390+
391+
// immediately return so the request isn't waiting.
392+
return Task.CompletedTask;
393+
}
394+
});
395+
}
396+
else
397+
{
398+
PerformRebuild(contentTypeIds, mediaTypeIds, memberTypeIds);
399+
}
400+
}
401+
402+
private void PerformRebuild(
403+
IReadOnlyCollection<int>? contentTypeIds = null,
404+
IReadOnlyCollection<int>? mediaTypeIds = null,
405+
IReadOnlyCollection<int>? memberTypeIds = null)
406+
{
407+
try
408+
{
409+
SetIsRebuilding();
410+
411+
_publishedContentService.Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds);
412+
}
413+
finally
414+
{
415+
ClearIsRebuilding();
416+
}
417+
}
418+
419+
private void SetIsRebuilding() => _runtimeCache.Insert(IsRebuildingDatabaseCacheRuntimeCacheKey, () => "tempValue", TimeSpan.FromMinutes(10));
420+
421+
private void ClearIsRebuilding() => _runtimeCache.Clear(IsRebuildingDatabaseCacheRuntimeCacheKey);
358422

359423
public async Task CollectAsync()
360424
{

src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public string GetStatus()
2929
$"The current {typeof(IPublishedSnapshotService)} is not the default type. A status cannot be determined.";
3030
}
3131

32+
if (_service.IsRebuilding())
33+
{
34+
return "Rebuild in progress. Please wait.";
35+
}
36+
3237
// TODO: This should be private
3338
_service.EnsureCaches();
3439

src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public class
2020
{
2121
private readonly ContentPermissions _contentPermissions;
2222

23+
protected override UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Document;
24+
2325
/// <summary>
2426
/// Initializes a new instance of the <see cref="ContentPermissionsQueryStringHandler" /> class.
2527
/// </summary>
@@ -48,7 +50,11 @@ protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context,
4850
return Task.FromResult(true);
4951
}
5052

51-
var argument = routeVal.ToString();
53+
// Handle case where the incoming querystring could contain more than one value (e.g. ?id=1000&id=1001).
54+
// It's the first one that'll be processed by the protected method so we should verify that.
55+
var argument = routeVal.Count == 1
56+
? routeVal.ToString()
57+
: routeVal.FirstOrDefault()?.ToString() ?? string.Empty;
5258

5359
if (!TryParseNodeId(argument, out nodeId))
5460
{

src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler<
1818
{
1919
private readonly MediaPermissions _mediaPermissions;
2020

21+
protected override UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Media;
22+
2123
/// <summary>
2224
/// Initializes a new instance of the <see cref="MediaPermissionsQueryStringHandler" /> class.
2325
/// </summary>
@@ -44,7 +46,11 @@ protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context,
4446
return Task.FromResult(true);
4547
}
4648

47-
var argument = routeVal.ToString();
49+
// Handle case where the incoming querystring could contain more than one value (e.g. ?id=1000&id=1001).
50+
// It's the first one that'll be processed by the protected method so we should verify that.
51+
var argument = routeVal.Count == 1
52+
? routeVal.ToString()
53+
: routeVal.FirstOrDefault()?.ToString() ?? string.Empty;
4854

4955
if (!TryParseNodeId(argument, out var nodeId))
5056
{

src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,18 @@ public PermissionsQueryStringHandler(
4949
/// </summary>
5050
protected IEntityService EntityService { get; set; }
5151

52+
/// <summary>
53+
/// Defaults to Unknown so all types are allowed, since Keys are unique across all node types this works,
54+
/// but it if you are certain you are looking for a specific type this should be overwritten for DB query performance.
55+
/// </summary>
56+
protected virtual UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Unknown;
57+
5258
/// <summary>
5359
/// Attempts to parse a node ID from a string representation found in a querystring value.
5460
/// </summary>
5561
/// <param name="argument">Querystring value.</param>
5662
/// <param name="nodeId">Output parsed Id.</param>
57-
/// <returns>True of node ID could be parased, false it not.</returns>
63+
/// <returns>True of node ID could be parsed, false it not.</returns>
5864
protected bool TryParseNodeId(string argument, out int nodeId)
5965
{
6066
// If the argument is an int, it will parse and can be assigned to nodeId.
@@ -75,7 +81,7 @@ protected bool TryParseNodeId(string argument, out int nodeId)
7581

7682
if (Guid.TryParse(argument, out Guid key))
7783
{
78-
nodeId = EntityService.GetId(key, UmbracoObjectTypes.Document).Result;
84+
nodeId = EntityService.GetId(key, KeyParsingFilterType).Result;
7985
return true;
8086
}
8187

0 commit comments

Comments
 (0)