Skip to content

Commit 3560fc9

Browse files
Merge branch 'v14/dev' into contrib
2 parents 80b8b2a + fd10060 commit 3560fc9

File tree

51 files changed

+1238
-97
lines changed

Some content is hidden

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

51 files changed

+1238
-97
lines changed

src/Umbraco.Cms.Api.Management/Controllers/Document/GetAuditLogDocumentController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public async Task<IActionResult> GetAuditLog(CancellationToken cancellationToken
3939
{
4040
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
4141
User,
42-
ContentPermissionResource.WithKeys(ActionProtect.ActionLetter, id),
42+
ContentPermissionResource.WithKeys(ActionBrowse.ActionLetter, id),
4343
AuthorizationPolicies.ContentPermissionByResource);
4444

4545
if (!authorizationResult.Succeeded)

src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDomainsController.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public async Task<IActionResult> Update(
6666
.WithDetail("One or more of the specified domain names were conflicting with domain assignments to other content items.")
6767
.WithExtension("conflictingDomainNames", _domainPresentationFactory.CreateDomainAssignmentModels(result.Result.ConflictingDomains.EmptyNull()))
6868
.Build()),
69+
DomainOperationStatus.InvalidDomainName => BadRequest(problemDetailsBuilder
70+
.WithTitle("Invalid domain name detected")
71+
.WithDetail("One or more of the specified domain names were invalid.")
72+
.Build()),
6973
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
7074
.WithTitle("Unknown domain update operation status.")
7175
.Build()),

src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@ status is ContentTypeOperationStatus.Success
9898
.WithTitle("Name was too long")
9999
.WithDetail("Name cannot be more than 255 characters in length.")
100100
.Build()),
101+
ContentTypeOperationStatus.InvalidElementFlagDocumentHasContent => new BadRequestObjectResult(problemDetailsBuilder
102+
.WithTitle("Invalid IsElement flag")
103+
.WithDetail("Cannot change to element type because content has already been created with this document type.")
104+
.Build()),
105+
ContentTypeOperationStatus.InvalidElementFlagElementIsUsedInPropertyEditorConfiguration => new BadRequestObjectResult(problemDetailsBuilder
106+
.WithTitle("Invalid IsElement flag")
107+
.WithDetail("Cannot change to document type because this element type is used in the configuration of a data type.")
108+
.Build()),
109+
ContentTypeOperationStatus.InvalidElementFlagComparedToParent => new BadRequestObjectResult(problemDetailsBuilder
110+
.WithTitle("Invalid IsElement flag")
111+
.WithDetail("Can not create a documentType with inheritance composition where the parent and the new type's IsElement flag are different.")
112+
.Build()),
101113
_ => new ObjectResult("Unknown content type operation status") { StatusCode = StatusCodes.Status500InternalServerError },
102114
});
103115

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45275,4 +45275,4 @@
4527545275
}
4527645276
}
4527745277
}
45278-
}
45278+
}

src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,11 +398,14 @@ private void AddCoreServices()
398398

399399
// Segments
400400
Services.AddUnique<ISegmentService, NoopSegmentService>();
401-
401+
402402
// definition Import/export
403403
Services.AddUnique<ITemporaryFileToXmlImportService, TemporaryFileToXmlImportService>();
404404
Services.AddUnique<IContentTypeImportService, ContentTypeImportService>();
405405
Services.AddUnique<IMediaTypeImportService, MediaTypeImportService>();
406+
407+
// add validation services
408+
Services.AddUnique<IElementSwitchValidator, ElementSwitchValidator>();
406409
}
407410
}
408411
}

src/Umbraco.Core/Extensions/ObjectExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,24 @@ public static Attempt<T> TryConvertTo<T>(this object? input)
219219
}
220220
}
221221

222+
if (target == typeof(DateTime) && input is DateTimeOffset dateTimeOffset)
223+
{
224+
// IMPORTANT: for compatability with various editors, we must discard any Offset information and assume UTC time here
225+
return Attempt.Succeed((object?)new DateTime(
226+
new DateOnly(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day),
227+
new TimeOnly(dateTimeOffset.Hour, dateTimeOffset.Minute, dateTimeOffset.Second, dateTimeOffset.Millisecond, dateTimeOffset.Microsecond),
228+
DateTimeKind.Utc));
229+
}
230+
231+
if (target == typeof(DateTimeOffset) && input is DateTime dateTime)
232+
{
233+
// IMPORTANT: for compatability with various editors, we must discard any DateTimeKind information and assume UTC time here
234+
return Attempt.Succeed((object?)new DateTimeOffset(
235+
new DateOnly(dateTime.Year, dateTime.Month, dateTime.Day),
236+
new TimeOnly(dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, dateTime.Microsecond),
237+
TimeSpan.Zero));
238+
}
239+
222240
TypeConverter? inputConverter = GetCachedSourceTypeConverter(inputType, target);
223241
if (inputConverter != null)
224242
{
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using Umbraco.Cms.Core.Events;
2+
using Umbraco.Cms.Core.Extensions;
3+
using Umbraco.Cms.Core.Models;
4+
using Umbraco.Cms.Core.Notifications;
5+
using Umbraco.Cms.Core.Services;
6+
using Umbraco.Cms.Core.Services.ContentTypeEditing;
7+
8+
namespace Umbraco.Cms.Core.Handlers;
9+
10+
public class WarnDocumentTypeElementSwitchNotificationHandler :
11+
INotificationAsyncHandler<ContentTypeSavingNotification>,
12+
INotificationAsyncHandler<ContentTypeSavedNotification>
13+
{
14+
private const string NotificationStateKey =
15+
"Umbraco.Cms.Core.Handlers.WarnDocumentTypeElementSwitchNotificationHandler";
16+
17+
private readonly IEventMessagesFactory _eventMessagesFactory;
18+
private readonly IContentTypeService _contentTypeService;
19+
private readonly IElementSwitchValidator _elementSwitchValidator;
20+
21+
public WarnDocumentTypeElementSwitchNotificationHandler(
22+
IEventMessagesFactory eventMessagesFactory,
23+
IContentTypeService contentTypeService,
24+
IElementSwitchValidator elementSwitchValidator)
25+
{
26+
_eventMessagesFactory = eventMessagesFactory;
27+
_contentTypeService = contentTypeService;
28+
_elementSwitchValidator = elementSwitchValidator;
29+
}
30+
31+
// To figure out whether a warning should be generated, we need both the state before and after saving
32+
public async Task HandleAsync(ContentTypeSavingNotification notification, CancellationToken cancellationToken)
33+
{
34+
IEnumerable<Guid> updatedKeys = notification.SavedEntities
35+
.Where(e => e.HasIdentity)
36+
.Select(e => e.Key);
37+
38+
IEnumerable<IContentType> persistedItems = _contentTypeService.GetAll(updatedKeys);
39+
40+
var stateInformation = persistedItems
41+
.ToDictionary(
42+
contentType => contentType.Key,
43+
contentType => new DocumentTypeElementSwitchInformation { WasElement = contentType.IsElement });
44+
notification.State[NotificationStateKey] = stateInformation;
45+
}
46+
47+
public async Task HandleAsync(ContentTypeSavedNotification notification, CancellationToken cancellationToken)
48+
{
49+
if (notification.State[NotificationStateKey] is not Dictionary<Guid, DocumentTypeElementSwitchInformation>
50+
stateInformation)
51+
{
52+
return;
53+
}
54+
55+
EventMessages eventMessages = _eventMessagesFactory.Get();
56+
57+
foreach (IContentType savedDocumentType in notification.SavedEntities)
58+
{
59+
if (stateInformation.ContainsKey(savedDocumentType.Key) is false)
60+
{
61+
continue;
62+
}
63+
64+
DocumentTypeElementSwitchInformation state = stateInformation[savedDocumentType.Key];
65+
if (state.WasElement == savedDocumentType.IsElement)
66+
{
67+
// no change
68+
continue;
69+
}
70+
71+
await WarnIfAncestorsAreMisaligned(savedDocumentType, eventMessages);
72+
await WarnIfDescendantsAreMisaligned(savedDocumentType, eventMessages);
73+
}
74+
}
75+
76+
private async Task WarnIfAncestorsAreMisaligned(IContentType contentType, EventMessages eventMessages)
77+
{
78+
if (await _elementSwitchValidator.AncestorsAreAlignedAsync(contentType) == false)
79+
{
80+
// todo update this message when the format has been agreed upon on with the client
81+
eventMessages.Add(new EventMessage(
82+
"DocumentType saved",
83+
"One or more ancestors have a mismatching element flag",
84+
EventMessageType.Warning));
85+
}
86+
}
87+
88+
private async Task WarnIfDescendantsAreMisaligned(IContentType contentType, EventMessages eventMessages)
89+
{
90+
if (await _elementSwitchValidator.DescendantsAreAlignedAsync(contentType) == false)
91+
{
92+
// todo update this message when the format has been agreed upon on with the client
93+
eventMessages.Add(new EventMessage(
94+
"DocumentType saved",
95+
"One or more descendants have a mismatching element flag",
96+
EventMessageType.Warning));
97+
}
98+
}
99+
100+
private class DocumentTypeElementSwitchInformation
101+
{
102+
public bool WasElement { get; set; }
103+
}
104+
}

src/Umbraco.Core/Models/IDataValueEditor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public interface IDataValueEditor
2727
/// </summary>
2828
bool SupportsReadOnly => false;
2929

30-
3130
/// <summary>
3231
/// Gets the validators to use to validate the edited value.
3332
/// </summary>
@@ -75,4 +74,6 @@ public interface IDataValueEditor
7574
XNode ConvertDbToXml(IPropertyType propertyType, object value);
7675

7776
string ConvertDbToString(IPropertyType propertyType, object? value);
77+
78+
IEnumerable<Guid> ConfiguredElementTypeKeys() => Enumerable.Empty<Guid>();
7879
}

src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
99
public class BlockListConfiguration
1010
{
1111
[ConfigurationField("blocks")]
12-
public BlockConfiguration[] Blocks { get; set; } = null!;
12+
public BlockConfiguration[] Blocks { get; set; } = Array.Empty<BlockConfiguration>();
1313

1414
[ConfigurationField("validationLimit")]
1515
public NumberRange ValidationLimit { get; set; } = new();

src/Umbraco.Core/PropertyEditors/DataEditor.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ public DataEditor(IDataValueEditorFactory dataValueEditorFactory)
7575
[DataMember(Name = "supportsReadOnly", IsRequired = true)]
7676
public bool SupportsReadOnly { get; set; }
7777

78+
// Adding a virtual method that wraps the default implementation allows derived classes
79+
// to override the default implementation without having to explicitly inherit the interface.
80+
public virtual bool SupportsConfigurableElements => false;
81+
7882
/// <inheritdoc />
7983
[IgnoreDataMember]
8084
public bool IsDeprecated { get; }

0 commit comments

Comments
 (0)