Skip to content

Commit 64ae0ab

Browse files
committed
Merge branch 'v17/dev' into v17/feature/preview-urls
2 parents 28e9200 + 5a0a2bf commit 64ae0ab

File tree

477 files changed

+13783
-7105
lines changed

Some content is hidden

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

477 files changed

+13783
-7105
lines changed

.github/workflows/azure-backoffice.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ on:
1717
- main
1818
- v*/dev
1919
- v*/main
20+
workflow_dispatch:
2021

2122
jobs:
2223
build_and_deploy_job:

.github/workflows/azure-storybook.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ on:
1717
- main
1818
- v*/dev
1919
- v*/main
20+
workflow_dispatch:
2021

2122
env:
2223
NODE_OPTIONS: --max_old_space_size=16384

NOTICES.txt

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ Third-Party Notices
33

44
This file contains notices and attributions for third-party software used in the Umbraco CMS project.
55

6-
It is not a license and does not grant any rights to use the third-party software.
6+
Third-party software may contain dependencies that are not explicitly listed here.
7+
8+
This notice is not a license and does not grant any rights to use the third-party software.
79

810
Umbraco CMS is licensed under the MIT License, which can be found in the LICENSE file.
911

@@ -113,14 +115,6 @@ Copyright: 2023 Shannon Deminick
113115

114116
---
115117

116-
Glob: A library for matching file paths using glob patterns
117-
118-
URL: https://github.com/isaacs/node-glob
119-
License: ISC License
120-
Copyright: 2009-2023 Isaac Z. Schlueter and Contributors
121-
122-
---
123-
124118
Globals: A library for managing global variables in JavaScript
125119

126120
URL: https://github.com/sindresorhus/globals
@@ -356,38 +350,6 @@ Copyright: Titus Wormer
356350

357351
---
358352

359-
Rollup: A module bundler for JavaScript
360-
361-
URL: https://rollupjs.org/
362-
License: MIT License
363-
Copyright: 2015-present Rollup contributors
364-
365-
---
366-
367-
Rollup Plugins: A collection of Rollup plugins
368-
369-
URL: https://github.com/rollup/plugins
370-
License: MIT License
371-
Copyright: 2019-present Rollup Plugins contributors
372-
373-
---
374-
375-
Rollup-plugin-esbuild: A Rollup plugin for using esbuild
376-
377-
URL: https://github.com/egoist/rollup-plugin-esbuild
378-
License: MIT License
379-
Copyright: 2020 EGOIST
380-
381-
---
382-
383-
Rollup-plugin-import-css: A Rollup plugin for importing CSS files
384-
385-
URL: https://github.com/jleeson/rollup-plugin-import-css
386-
License: MIT License
387-
Copyright: 2020 Jacob Leeson
388-
389-
---
390-
391353
rxjs: Reactive Extensions for JavaScript
392354

393355
URL: https://rxjs.dev/

src/Umbraco.Cms.Api.Management/Controllers/User/UserOrCurrentUserControllerBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected IActionResult UserOperationStatusResult(UserOperationStatus status, Er
2525
.Build()),
2626
UserOperationStatus.NoUserGroup => BadRequest(problemDetailsBuilder
2727
.WithTitle("No User Group Specified")
28-
.WithDetail("A user group must be specified to create a user")
28+
.WithDetail("A user must be assigned to at least one group")
2929
.Build()),
3030
UserOperationStatus.UserNameIsNotEmail => BadRequest(problemDetailsBuilder
3131
.WithTitle("Invalid Username")

src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseCreator.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public void Create(string connectionString)
8686
// Copy our blank(ish) wal mode sqlite database to its final location.
8787
try
8888
{
89+
EnsureDatabaseDirectory(original.DataSource);
8990
File.Copy(tempFile, original.DataSource, true);
9091
}
9192
catch (Exception ex)
@@ -104,4 +105,13 @@ public void Create(string connectionString)
104105
_logger.LogWarning(ex, "Unable to cleanup temporary sqlite database file {path}", tempFile);
105106
}
106107
}
108+
109+
private static void EnsureDatabaseDirectory(string dataSource)
110+
{
111+
var directoryPath = Path.GetDirectoryName(dataSource);
112+
if (string.IsNullOrEmpty(directoryPath) is false && Directory.Exists(directoryPath) is false)
113+
{
114+
Directory.CreateDirectory(directoryPath);
115+
}
116+
}
107117
}

src/Umbraco.Core/Services/UserService.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ private async Task<UserOperationStatus> ValidateUserCreateModel(UserCreateModel
883883
return UserOperationStatus.DuplicateUserName;
884884
}
885885

886-
if(model.UserGroupKeys.Count == 0)
886+
if (model.UserGroupKeys.Count == 0)
887887
{
888888
return UserOperationStatus.NoUserGroup;
889889
}
@@ -912,6 +912,13 @@ private async Task<UserOperationStatus> ValidateUserCreateModel(UserCreateModel
912912
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.MissingUser, existingUser);
913913
}
914914

915+
// A user must remain assigned to at least one group.
916+
if (model.UserGroupKeys.Count == 0)
917+
{
918+
scope.Complete();
919+
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.NoUserGroup, existingUser);
920+
}
921+
915922
// User names can only contain the configured allowed characters. This is validated by ASP.NET Identity on create
916923
// as the setting is applied to the BackOfficeIdentityOptions, but we need to check ourselves for updates.
917924
var allowedUserNameCharacters = _securitySettings.AllowedUserNameCharacters;
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
using Umbraco.Cms.Core.Models;
1+
using Umbraco.Cms.Core.Models;
22
using Umbraco.Cms.Core.Models.PublishedContent;
33

44
namespace Umbraco.Cms.Infrastructure.HybridCache.Factories;
55

6+
/// <summary>
7+
/// Defines a factory to create <see cref="IPublishedContent"/> and <see cref="IPublishedMember"/> from a <see cref="ContentCacheNode"/> or <see cref="IMember"/>.
8+
/// </summary>
69
internal interface IPublishedContentFactory
710
{
11+
/// <summary>
12+
/// Converts a <see cref="ContentCacheNode"/> to an <see cref="IPublishedContent"/> if document type.
13+
/// </summary>
814
IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview);
15+
16+
/// <summary>
17+
/// Converts a <see cref="ContentCacheNode"/> to an <see cref="IPublishedContent"/> of media type.
18+
/// </summary>
919
IPublishedContent? ToIPublishedMedia(ContentCacheNode contentCacheNode);
1020

21+
/// <summary>
22+
/// Converts a <see cref="IMember"/> to an <see cref="IPublishedMember"/>.
23+
/// </summary>
1124
IPublishedMember ToPublishedMember(IMember member);
1225
}

src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,62 @@
1+
using Microsoft.Extensions.Logging;
2+
using Umbraco.Cms.Core.Cache;
13
using Umbraco.Cms.Core.Extensions;
24
using Umbraco.Cms.Core.Models;
35
using Umbraco.Cms.Core.Models.PublishedContent;
46
using Umbraco.Cms.Core.PublishedCache;
7+
using Umbraco.Extensions;
58

69
namespace Umbraco.Cms.Infrastructure.HybridCache.Factories;
710

11+
/// <summary>
12+
/// Defines a factory to create <see cref="IPublishedContent"/> and <see cref="IPublishedMember"/> from a <see cref="ContentCacheNode"/> or <see cref="IMember"/>.
13+
/// </summary>
814
internal sealed class PublishedContentFactory : IPublishedContentFactory
915
{
1016
private readonly IElementsCache _elementsCache;
1117
private readonly IVariationContextAccessor _variationContextAccessor;
1218
private readonly IPublishedContentTypeCache _publishedContentTypeCache;
19+
private readonly ILogger<PublishedContentFactory> _logger;
20+
private readonly AppCaches _appCaches;
1321

14-
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="PublishedContentFactory"/> class.
24+
/// </summary>
1525
public PublishedContentFactory(
1626
IElementsCache elementsCache,
1727
IVariationContextAccessor variationContextAccessor,
18-
IPublishedContentTypeCache publishedContentTypeCache)
28+
IPublishedContentTypeCache publishedContentTypeCache,
29+
ILogger<PublishedContentFactory> logger,
30+
AppCaches appCaches)
1931
{
2032
_elementsCache = elementsCache;
2133
_variationContextAccessor = variationContextAccessor;
2234
_publishedContentTypeCache = publishedContentTypeCache;
35+
_logger = logger;
36+
_appCaches = appCaches;
2337
}
2438

39+
/// <inheritdoc/>
2540
public IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview)
2641
{
27-
IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Content, contentCacheNode.ContentTypeId);
42+
var cacheKey = $"{nameof(PublishedContentFactory)}DocumentCache_{contentCacheNode.Id}_{preview}";
43+
IPublishedContent? publishedContent = _appCaches.RequestCache.GetCacheItem<IPublishedContent?>(cacheKey);
44+
if (publishedContent is not null)
45+
{
46+
_logger.LogDebug(
47+
"Using cached IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).",
48+
contentCacheNode.Data?.Name ?? "No Name",
49+
contentCacheNode.Id);
50+
return publishedContent;
51+
}
52+
53+
_logger.LogDebug(
54+
"Creating IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).",
55+
contentCacheNode.Data?.Name ?? "No Name",
56+
contentCacheNode.Id);
57+
58+
IPublishedContentType contentType =
59+
_publishedContentTypeCache.Get(PublishedItemType.Content, contentCacheNode.ContentTypeId);
2860
var contentNode = new ContentNode(
2961
contentCacheNode.Id,
3062
contentCacheNode.Key,
@@ -35,19 +67,42 @@ public PublishedContentFactory(
3567
preview ? contentCacheNode.Data : null,
3668
preview ? null : contentCacheNode.Data);
3769

38-
IPublishedContent? model = GetModel(contentNode, preview);
70+
publishedContent = GetModel(contentNode, preview);
3971

4072
if (preview)
4173
{
42-
return model ?? GetPublishedContentAsDraft(model);
74+
publishedContent ??= GetPublishedContentAsDraft(publishedContent);
75+
}
76+
77+
if (publishedContent is not null)
78+
{
79+
_appCaches.RequestCache.Set(cacheKey, publishedContent);
4380
}
4481

45-
return model;
82+
return publishedContent;
4683
}
4784

85+
/// <inheritdoc/>
4886
public IPublishedContent? ToIPublishedMedia(ContentCacheNode contentCacheNode)
4987
{
50-
IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Media, contentCacheNode.ContentTypeId);
88+
var cacheKey = $"{nameof(PublishedContentFactory)}MediaCache_{contentCacheNode.Id}";
89+
IPublishedContent? publishedContent = _appCaches.RequestCache.GetCacheItem<IPublishedContent?>(cacheKey);
90+
if (publishedContent is not null)
91+
{
92+
_logger.LogDebug(
93+
"Using cached IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).",
94+
contentCacheNode.Data?.Name ?? "No Name",
95+
contentCacheNode.Id);
96+
return publishedContent;
97+
}
98+
99+
_logger.LogDebug(
100+
"Creating IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).",
101+
contentCacheNode.Data?.Name ?? "No Name",
102+
contentCacheNode.Id);
103+
104+
IPublishedContentType contentType =
105+
_publishedContentTypeCache.Get(PublishedItemType.Media, contentCacheNode.ContentTypeId);
51106
var contentNode = new ContentNode(
52107
contentCacheNode.Id,
53108
contentCacheNode.Key,
@@ -58,12 +113,38 @@ public PublishedContentFactory(
58113
null,
59114
contentCacheNode.Data);
60115

61-
return GetModel(contentNode, false);
116+
publishedContent = GetModel(contentNode, false);
117+
118+
if (publishedContent is not null)
119+
{
120+
_appCaches.RequestCache.Set(cacheKey, publishedContent);
121+
}
122+
123+
return publishedContent;
62124
}
63125

126+
/// <inheritdoc/>
64127
public IPublishedMember ToPublishedMember(IMember member)
65128
{
66-
IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId);
129+
string cacheKey = $"{nameof(PublishedContentFactory)}MemberCache_{member.Id}";
130+
IPublishedMember? publishedMember = _appCaches.RequestCache.GetCacheItem<IPublishedMember?>(cacheKey);
131+
if (publishedMember is not null)
132+
{
133+
_logger.LogDebug(
134+
"Using cached IPublishedMember for member {MemberName} ({MemberId}).",
135+
member.Username,
136+
member.Id);
137+
138+
return publishedMember;
139+
}
140+
141+
_logger.LogDebug(
142+
"Creating IPublishedMember for member {MemberName} ({MemberId}).",
143+
member.Username,
144+
member.Id);
145+
146+
IPublishedContentType contentType =
147+
_publishedContentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId);
67148

68149
// Members are only "mapped" never cached, so these default values are a bit weird, but they are not used.
69150
var contentData = new ContentData(
@@ -86,7 +167,11 @@ public IPublishedMember ToPublishedMember(IMember member)
86167
contentType,
87168
null,
88169
contentData);
89-
return new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor);
170+
publishedMember = new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor);
171+
172+
_appCaches.RequestCache.Set(cacheKey, publishedMember);
173+
174+
return publishedMember;
90175
}
91176

92177
private static Dictionary<string, PropertyData[]> GetPropertyValues(IPublishedContentType contentType, IMember member)
@@ -135,7 +220,6 @@ private static void AddIf(IPublishedContentType contentType, IDictionary<string,
135220
_variationContextAccessor);
136221
}
137222

138-
139223
private static IPublishedContent? GetPublishedContentAsDraft(IPublishedContent? content) =>
140224
content == null ? null :
141225
// an object in the cache is either an IPublishedContentOrMedia,
@@ -150,7 +234,7 @@ private static PublishedContent UnwrapIPublishedContent(IPublishedContent conten
150234
content = wrapped.Unwrap();
151235
}
152236

153-
if (!(content is PublishedContent inner))
237+
if (content is not PublishedContent inner)
154238
{
155239
throw new InvalidOperationException("Innermost content is not PublishedContent.");
156240
}

0 commit comments

Comments
 (0)