Skip to content

Commit 671751b

Browse files
Merge branch 'v15/dev' into contrib
2 parents 1564313 + 3b6e4a9 commit 671751b

File tree

71 files changed

+1628
-199
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1628
-199
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Umbraco.Cms.Api.Management.ViewModels.DocumentBlueprint;
6+
using Umbraco.Cms.Core.Mapping;
7+
using Umbraco.Cms.Core.Models;
8+
using Umbraco.Cms.Core.Services;
9+
using Umbraco.Cms.Web.Common.Authorization;
10+
11+
namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint;
12+
13+
[ApiVersion("1.0")]
14+
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
15+
public class ScaffoldDocumentBlueprintController : DocumentBlueprintControllerBase
16+
{
17+
private readonly IContentBlueprintEditingService _contentBlueprintEditingService;
18+
private readonly IUmbracoMapper _umbracoMapper;
19+
20+
public ScaffoldDocumentBlueprintController(IContentBlueprintEditingService contentBlueprintEditingService, IUmbracoMapper umbracoMapper)
21+
{
22+
_contentBlueprintEditingService = contentBlueprintEditingService;
23+
_umbracoMapper = umbracoMapper;
24+
}
25+
26+
[HttpGet("{id:guid}/scaffold")]
27+
[MapToApiVersion("1.0")]
28+
[ProducesResponseType(typeof(DocumentBlueprintResponseModel), StatusCodes.Status200OK)]
29+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
30+
public async Task<IActionResult> Scaffold(CancellationToken cancellationToken, Guid id)
31+
{
32+
IContent? blueprint = await _contentBlueprintEditingService.GetScaffoldedAsync(id);
33+
return blueprint is not null
34+
? Ok(_umbracoMapper.Map<DocumentBlueprintResponseModel>(blueprint))
35+
: DocumentBlueprintNotFound();
36+
}
37+
}

src/Umbraco.Cms.Api.Management/OpenApi.json

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3485,6 +3485,66 @@
34853485
]
34863486
}
34873487
},
3488+
"/umbraco/management/api/v1/document-blueprint/{id}/scaffold": {
3489+
"get": {
3490+
"tags": [
3491+
"Document Blueprint"
3492+
],
3493+
"operationId": "GetDocumentBlueprintByIdScaffold",
3494+
"parameters": [
3495+
{
3496+
"name": "id",
3497+
"in": "path",
3498+
"required": true,
3499+
"schema": {
3500+
"type": "string",
3501+
"format": "uuid"
3502+
}
3503+
}
3504+
],
3505+
"responses": {
3506+
"200": {
3507+
"description": "OK",
3508+
"content": {
3509+
"application/json": {
3510+
"schema": {
3511+
"oneOf": [
3512+
{
3513+
"$ref": "#/components/schemas/DocumentBlueprintResponseModel"
3514+
}
3515+
]
3516+
}
3517+
}
3518+
}
3519+
},
3520+
"404": {
3521+
"description": "Not Found",
3522+
"content": {
3523+
"application/json": {
3524+
"schema": {
3525+
"oneOf": [
3526+
{
3527+
"$ref": "#/components/schemas/ProblemDetails"
3528+
}
3529+
]
3530+
}
3531+
}
3532+
}
3533+
},
3534+
"401": {
3535+
"description": "The resource is protected and requires an authentication token"
3536+
},
3537+
"403": {
3538+
"description": "The authenticated user does not have access to this resource"
3539+
}
3540+
},
3541+
"security": [
3542+
{
3543+
"Backoffice User": [ ]
3544+
}
3545+
]
3546+
}
3547+
},
34883548
"/umbraco/management/api/v1/document-blueprint/folder": {
34893549
"post": {
34903550
"tags": [

src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Umbraco.Cms.Core.DependencyInjection;
13
using Umbraco.Cms.Core.Events;
24
using Umbraco.Cms.Core.Models;
35
using Umbraco.Cms.Core.Notifications;
@@ -21,9 +23,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
2123
private readonly IDocumentNavigationManagementService _documentNavigationManagementService;
2224
private readonly IContentService _contentService;
2325
private readonly IDocumentCacheService _documentCacheService;
26+
private readonly ICacheManager _cacheManager;
2427
private readonly IPublishStatusManagementService _publishStatusManagementService;
2528
private readonly IIdKeyMap _idKeyMap;
2629

30+
[Obsolete("Use the constructor with ICacheManager instead, scheduled for removal in V17.")]
2731
public ContentCacheRefresher(
2832
AppCaches appCaches,
2933
IJsonSerializer serializer,
@@ -38,6 +42,39 @@ public ContentCacheRefresher(
3842
IContentService contentService,
3943
IPublishStatusManagementService publishStatusManagementService,
4044
IDocumentCacheService documentCacheService)
45+
: this(
46+
appCaches,
47+
serializer,
48+
idKeyMap,
49+
domainService,
50+
eventAggregator,
51+
factory,
52+
documentUrlService,
53+
domainCacheService,
54+
documentNavigationQueryService,
55+
documentNavigationManagementService,
56+
contentService,
57+
publishStatusManagementService,
58+
documentCacheService,
59+
StaticServiceProvider.Instance.GetRequiredService<ICacheManager>())
60+
{
61+
}
62+
63+
public ContentCacheRefresher(
64+
AppCaches appCaches,
65+
IJsonSerializer serializer,
66+
IIdKeyMap idKeyMap,
67+
IDomainService domainService,
68+
IEventAggregator eventAggregator,
69+
ICacheRefresherNotificationFactory factory,
70+
IDocumentUrlService documentUrlService,
71+
IDomainCacheService domainCacheService,
72+
IDocumentNavigationQueryService documentNavigationQueryService,
73+
IDocumentNavigationManagementService documentNavigationManagementService,
74+
IContentService contentService,
75+
IPublishStatusManagementService publishStatusManagementService,
76+
IDocumentCacheService documentCacheService,
77+
ICacheManager cacheManager)
4178
: base(appCaches, serializer, eventAggregator, factory)
4279
{
4380
_idKeyMap = idKeyMap;
@@ -49,6 +86,11 @@ public ContentCacheRefresher(
4986
_contentService = contentService;
5087
_documentCacheService = documentCacheService;
5188
_publishStatusManagementService = publishStatusManagementService;
89+
90+
// TODO: Ideally we should inject IElementsCache
91+
// this interface is in infrastructure, and changing this is very breaking
92+
// so as long as we have the cache manager, which casts the IElementsCache to a simple AppCache we might as well use that.
93+
_cacheManager = cacheManager;
5294
}
5395

5496
#region Indirect
@@ -83,6 +125,13 @@ public override void Refresh(JsonPayload[] payloads)
83125
AppCaches.RuntimeCache.ClearOfType<PublicAccessEntry>();
84126
AppCaches.RuntimeCache.ClearByKey(CacheKeys.ContentRecycleBinCacheKey);
85127

128+
// Ideally, we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
129+
// The reason for this is that we have no way to know which elements are affected by the changes or what their keys are.
130+
// This is because currently published elements live exclusively in a JSON blob in the umbracoPropertyData table.
131+
// This means that the only way to resolve these keys is to actually parse this data with a specific value converter, and for all cultures, which is not possible.
132+
// If published elements become their own entities with relations, instead of just property data, we can revisit this.
133+
_cacheManager.ElementsCache.Clear();
134+
86135
var idsRemoved = new HashSet<int>();
87136
IAppPolicyCache isolatedCache = AppCaches.IsolatedCaches.GetOrCreate<IContent>();
88137

src/Umbraco.Core/Cache/Refreshers/Implement/MediaCacheRefresher.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Umbraco.Cms.Core.DependencyInjection;
13
using Umbraco.Cms.Core.Events;
24
using Umbraco.Cms.Core.Models;
35
using Umbraco.Cms.Core.Notifications;
@@ -18,7 +20,9 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
1820
private readonly IMediaNavigationManagementService _mediaNavigationManagementService;
1921
private readonly IMediaService _mediaService;
2022
private readonly IMediaCacheService _mediaCacheService;
23+
private readonly ICacheManager _cacheManager;
2124

25+
[Obsolete("Use the constructor with ICacheManager instead, scheduled for removal in V17.")]
2226
public MediaCacheRefresher(
2327
AppCaches appCaches,
2428
IJsonSerializer serializer,
@@ -29,13 +33,41 @@ public MediaCacheRefresher(
2933
IMediaNavigationManagementService mediaNavigationManagementService,
3034
IMediaService mediaService,
3135
IMediaCacheService mediaCacheService)
36+
: this(
37+
appCaches,
38+
serializer,
39+
idKeyMap,
40+
eventAggregator,
41+
factory,
42+
mediaNavigationQueryService,
43+
mediaNavigationManagementService,
44+
mediaService,
45+
mediaCacheService,
46+
StaticServiceProvider.Instance.GetRequiredService<ICacheManager>())
47+
{
48+
}
49+
50+
public MediaCacheRefresher(
51+
AppCaches appCaches,
52+
IJsonSerializer serializer,
53+
IIdKeyMap idKeyMap,
54+
IEventAggregator eventAggregator,
55+
ICacheRefresherNotificationFactory factory,
56+
IMediaNavigationQueryService mediaNavigationQueryService,
57+
IMediaNavigationManagementService mediaNavigationManagementService,
58+
IMediaService mediaService,
59+
IMediaCacheService mediaCacheService,
60+
ICacheManager cacheManager)
3261
: base(appCaches, serializer, eventAggregator, factory)
3362
{
3463
_idKeyMap = idKeyMap;
3564
_mediaNavigationQueryService = mediaNavigationQueryService;
3665
_mediaNavigationManagementService = mediaNavigationManagementService;
3766
_mediaService = mediaService;
3867
_mediaCacheService = mediaCacheService;
68+
69+
// TODO: Use IElementsCache instead of ICacheManager, see ContentCacheRefresher for more information.
70+
_cacheManager = cacheManager;
3971
}
4072

4173
#region Indirect
@@ -87,6 +119,13 @@ public override void Refresh(JsonPayload[]? payloads)
87119
AppCaches.RuntimeCache.ClearByKey(CacheKeys.MediaRecycleBinCacheKey);
88120
Attempt<IAppPolicyCache?> mediaCache = AppCaches.IsolatedCaches.Get<IMedia>();
89121

122+
// Ideally, we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
123+
// The reason for this is that we have no way to know which elements are affected by the changes or what their keys are.
124+
// This is because currently published elements live exclusively in a JSON blob in the umbracoPropertyData table.
125+
// This means that the only way to resolve these keys is to actually parse this data with a specific value converter, and for all cultures, which is not possible.
126+
// If published elements become their own entities with relations, instead of just property data, we can revisit this.
127+
_cacheManager.ElementsCache.Clear();
128+
90129
foreach (JsonPayload payload in payloads)
91130
{
92131
if (payload.ChangeTypes == TreeChangeTypes.Remove)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@
377377
<key alias="invalidEmpty">Value cannot be empty</key>
378378
<key alias="invalidPattern">Value is invalid, it does not match the correct pattern</key>
379379
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
380-
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
380+
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
381381
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
382382
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
383383
<key alias="invalidMemberGroupName">Invalid member group name</key>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@
394394
<key alias="unexpectedRange">The value %0% is not expected to contain a range</key>
395395
<key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key>
396396
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
397-
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
397+
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
398398
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
399399
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
400400
<key alias="invalidMediaType">The chosen media type is invalid.</key>

src/Umbraco.Core/Extensions/ContentExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,13 @@ public static ContentStatus GetStatus(this IContent content, ContentScheduleColl
245245
}
246246

247247
IEnumerable<ContentSchedule> expires = contentSchedule.GetSchedule(culture, ContentScheduleAction.Expire);
248-
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date))
248+
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.UtcNow > x.Date))
249249
{
250250
return ContentStatus.Expired;
251251
}
252252

253253
IEnumerable<ContentSchedule> release = contentSchedule.GetSchedule(culture, ContentScheduleAction.Release);
254-
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now))
254+
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.UtcNow))
255255
{
256256
return ContentStatus.AwaitingRelease;
257257
}

src/Umbraco.Core/Services/ContentBlueprintEditingService.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using Microsoft.Extensions.Logging;
22
using Microsoft.Extensions.Options;
33
using Umbraco.Cms.Core.Configuration.Models;
4+
using Umbraco.Cms.Core.Events;
45
using Umbraco.Cms.Core.Models;
56
using Umbraco.Cms.Core.Models.ContentEditing;
7+
using Umbraco.Cms.Core.Notifications;
68
using Umbraco.Cms.Core.PropertyEditors;
79
using Umbraco.Cms.Core.Scoping;
810
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -35,6 +37,21 @@ public ContentBlueprintEditingService(
3537
return await Task.FromResult(blueprint);
3638
}
3739

40+
public Task<IContent?> GetScaffoldedAsync(Guid key)
41+
{
42+
IContent? blueprint = ContentService.GetBlueprintById(key);
43+
if (blueprint is null)
44+
{
45+
return Task.FromResult<IContent?>(null);
46+
}
47+
48+
using ICoreScope scope = CoreScopeProvider.CreateCoreScope();
49+
scope.Notifications.Publish(new ContentScaffoldedNotification(blueprint, blueprint, Constants.System.Root, new EventMessages()));
50+
scope.Complete();
51+
52+
return Task.FromResult<IContent?>(blueprint);
53+
}
54+
3855
public async Task<Attempt<PagedModel<IContent>?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(Guid contentTypeKey, int skip, int take)
3956
{
4057
IContentType? contentType = await ContentTypeService.GetAsync(contentTypeKey);

src/Umbraco.Core/Services/IContentBlueprintEditingService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ public interface IContentBlueprintEditingService
88
{
99
Task<IContent?> GetAsync(Guid key);
1010

11+
Task<IContent?> GetScaffoldedAsync(Guid key) => Task.FromResult<IContent?>(null);
12+
1113
Task<Attempt<PagedModel<IContent>?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(
1214
Guid contentTypeKey,
1315
int skip,

src/Umbraco.Infrastructure/Persistence/Dtos/AccessDto.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ internal class AccessDto
2727
[ForeignKey(typeof(NodeDto), Name = "FK_umbracoAccess_umbracoNode_id2")]
2828
public int NoAccessNodeId { get; set; }
2929

30-
[Column("createDate")]
30+
[Column("createDate", ForceToUtc = false)]
3131
[Constraint(Default = SystemMethods.CurrentDateTime)]
3232
public DateTime CreateDate { get; set; }
3333

34-
[Column("updateDate")]
34+
[Column("updateDate", ForceToUtc = false)]
3535
[Constraint(Default = SystemMethods.CurrentDateTime)]
3636
public DateTime UpdateDate { get; set; }
3737

0 commit comments

Comments
 (0)