Merge branch 'main' into v17/dev
# Conflicts: # src/Umbraco.Core/Services/DataTypeService.cs
This commit is contained in:
@@ -53,6 +53,7 @@ using Umbraco.Cms.Infrastructure.Migrations;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Install;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Relations;
|
||||
using Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers;
|
||||
using Umbraco.Cms.Infrastructure.Routing;
|
||||
using Umbraco.Cms.Infrastructure.Runtime;
|
||||
@@ -434,6 +435,13 @@ public static partial class UmbracoBuilderExtensions
|
||||
.AddNotificationAsyncHandler<ContentTypeSavingNotification, WarnDocumentTypeElementSwitchNotificationHandler>()
|
||||
.AddNotificationAsyncHandler<ContentTypeSavedNotification, WarnDocumentTypeElementSwitchNotificationHandler>();
|
||||
|
||||
// Handles for relation persistence on content save.
|
||||
builder
|
||||
.AddNotificationHandler<ContentSavedNotification, ContentRelationsUpdate>()
|
||||
.AddNotificationHandler<ContentPublishedNotification, ContentRelationsUpdate>()
|
||||
.AddNotificationHandler<MediaSavedNotification, ContentRelationsUpdate>()
|
||||
.AddNotificationHandler<MemberSavedNotification, ContentRelationsUpdate>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
@@ -31,8 +32,30 @@ internal static class RichTextEditorValueExtensions
|
||||
{
|
||||
foreach (BlockPropertyValue item in dataItem.Values)
|
||||
{
|
||||
item.PropertyType = elementTypes.FirstOrDefault(x => x.Key == dataItem.ContentTypeKey)?.PropertyTypes.FirstOrDefault(pt => pt.Alias == item.Alias);
|
||||
if (TryResolvePropertyType(elementTypes, dataItem.ContentTypeKey, item.Alias, out IPropertyType? resolvedPropertyType))
|
||||
{
|
||||
item.PropertyType = resolvedPropertyType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryResolvePropertyType(IEnumerable<IContentType> elementTypes, Guid contentTypeKey, string propertyTypeAlias, [NotNullWhen(true)] out IPropertyType? propertyType)
|
||||
{
|
||||
IContentType? elementType = elementTypes.FirstOrDefault(x => x.Key == contentTypeKey);
|
||||
if (elementType is null)
|
||||
{
|
||||
propertyType = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
propertyType = elementType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias);
|
||||
if (propertyType is not null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
propertyType = elementType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias);
|
||||
return propertyType is not null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ public class CustomConnectionStringDatabaseProviderMetadata : IDatabaseProviderM
|
||||
/// <inheritdoc />
|
||||
public bool SupportsIntegratedAuthentication => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsTrustServerCertificate => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool RequiresConnectionTest => true;
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ public static class DatabaseProviderMetadataExtensions
|
||||
Server = server ?? string.Empty,
|
||||
Login = login ?? string.Empty,
|
||||
Password = password ?? string.Empty,
|
||||
IntegratedAuth = integratedAuth == true && databaseProviderMetadata.SupportsIntegratedAuthentication
|
||||
IntegratedAuth = integratedAuth == true && databaseProviderMetadata.SupportsIntegratedAuthentication,
|
||||
TrustServerCertificate = databaseProviderMetadata.SupportsTrustServerCertificate,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,17 +11,23 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories;
|
||||
|
||||
internal static class DataTypeFactory
|
||||
{
|
||||
public static IDataType BuildEntity(DataTypeDto dto, PropertyEditorCollection editors, ILogger<IDataType> logger, IConfigurationEditorJsonSerializer serializer)
|
||||
public static IDataType BuildEntity(
|
||||
DataTypeDto dto,
|
||||
PropertyEditorCollection editors,
|
||||
ILogger<IDataType> logger,
|
||||
IConfigurationEditorJsonSerializer serializer,
|
||||
IDataValueEditorFactory dataValueEditorFactory)
|
||||
{
|
||||
// Check we have an editor for the data type.
|
||||
if (!editors.TryGet(dto.EditorAlias, out IDataEditor? editor))
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Could not find an editor with alias {EditorAlias}, treating as Label. " + "The site may fail to boot and/or load data types and run.", dto.EditorAlias);
|
||||
|
||||
// Create as special type, which downstream can be handled by converting to a LabelPropertyEditor to make clear
|
||||
// the situation to the user.
|
||||
editor = new MissingPropertyEditor();
|
||||
"Could not find an editor with alias {EditorAlias}, treating as Missing. " + "The site may fail to boot and/or load data types and run.",
|
||||
dto.EditorAlias);
|
||||
editor =
|
||||
new MissingPropertyEditor(
|
||||
dto.EditorAlias,
|
||||
dataValueEditorFactory);
|
||||
}
|
||||
|
||||
var dataType = new DataType(editor, serializer);
|
||||
@@ -42,7 +48,7 @@ internal static class DataTypeFactory
|
||||
dataType.SortOrder = dto.NodeDto.SortOrder;
|
||||
dataType.Trashed = dto.NodeDto.Trashed;
|
||||
dataType.CreatorId = dto.NodeDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
dataType.EditorUiAlias = dto.EditorUiAlias;
|
||||
dataType.EditorUiAlias = editor is MissingPropertyEditor ? "Umb.PropertyEditorUi.Missing" : dto.EditorUiAlias;
|
||||
|
||||
dataType.SetConfigurationData(editor.GetConfigurationEditor().FromDatabase(dto.Configuration, serializer));
|
||||
|
||||
|
||||
@@ -71,6 +71,12 @@ public interface IDatabaseProviderMetadata
|
||||
[DataMember(Name = "supportsIntegratedAuthentication")]
|
||||
bool SupportsIntegratedAuthentication { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether "Trust the database certificate" is supported (e.g. SQL Server & Oracle).
|
||||
/// </summary>
|
||||
[DataMember(Name = "supportsTrustServerCertificate")]
|
||||
bool SupportsTrustServerCertificate => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the connection should be tested before continuing install process.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence.Relations;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a notification handler for content saved operations that persists relations.
|
||||
/// </summary>
|
||||
internal sealed class ContentRelationsUpdate :
|
||||
IDistributedCacheNotificationHandler<ContentSavedNotification>,
|
||||
IDistributedCacheNotificationHandler<ContentPublishedNotification>,
|
||||
IDistributedCacheNotificationHandler<MediaSavedNotification>,
|
||||
IDistributedCacheNotificationHandler<MemberSavedNotification>
|
||||
{
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly IRelationRepository _relationRepository;
|
||||
private readonly IRelationTypeRepository _relationTypeRepository;
|
||||
private readonly ILogger<ContentRelationsUpdate> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentRelationsUpdate"/> class.
|
||||
/// </summary>
|
||||
public ContentRelationsUpdate(
|
||||
IScopeProvider scopeProvider,
|
||||
DataValueReferenceFactoryCollection dataValueReferenceFactories,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
IRelationRepository relationRepository,
|
||||
IRelationTypeRepository relationTypeRepository,
|
||||
ILogger<ContentRelationsUpdate> logger)
|
||||
{
|
||||
_scopeProvider = scopeProvider;
|
||||
_dataValueReferenceFactories = dataValueReferenceFactories;
|
||||
_propertyEditors = propertyEditors;
|
||||
_relationRepository = relationRepository;
|
||||
_relationTypeRepository = relationTypeRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(ContentSavedNotification notification) => PersistRelations(notification.SavedEntities);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(IEnumerable<ContentSavedNotification> notifications) => PersistRelations(notifications.SelectMany(x => x.SavedEntities));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(ContentPublishedNotification notification) => PersistRelations(notification.PublishedEntities);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(IEnumerable<ContentPublishedNotification> notifications) => PersistRelations(notifications.SelectMany(x => x.PublishedEntities));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(MediaSavedNotification notification) => PersistRelations(notification.SavedEntities);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(IEnumerable<MediaSavedNotification> notifications) => PersistRelations(notifications.SelectMany(x => x.SavedEntities));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(MemberSavedNotification notification) => PersistRelations(notification.SavedEntities);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(IEnumerable<MemberSavedNotification> notifications) => PersistRelations(notifications.SelectMany(x => x.SavedEntities));
|
||||
|
||||
private void PersistRelations(IEnumerable<IContentBase> entities)
|
||||
{
|
||||
using IScope scope = _scopeProvider.CreateScope();
|
||||
foreach (IContentBase entity in entities)
|
||||
{
|
||||
PersistRelations(scope, entity);
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
private void PersistRelations(IScope scope, IContentBase entity)
|
||||
{
|
||||
// Get all references and automatic relation type aliases.
|
||||
ISet<UmbracoEntityReference> references = _dataValueReferenceFactories.GetAllReferences(entity.Properties, _propertyEditors);
|
||||
ISet<string> automaticRelationTypeAliases = _dataValueReferenceFactories.GetAllAutomaticRelationTypesAliases(_propertyEditors);
|
||||
|
||||
if (references.Count == 0)
|
||||
{
|
||||
// Delete all relations using the automatic relation type aliases.
|
||||
_relationRepository.DeleteByParent(entity.Id, automaticRelationTypeAliases.ToArray());
|
||||
|
||||
// No need to add new references/relations
|
||||
return;
|
||||
}
|
||||
|
||||
// Lookup all relation type IDs.
|
||||
var relationTypeLookup = _relationTypeRepository.GetMany(Array.Empty<int>())
|
||||
.Where(x => automaticRelationTypeAliases.Contains(x.Alias))
|
||||
.ToDictionary(x => x.Alias, x => x.Id);
|
||||
|
||||
// Lookup node IDs for all GUID based UDIs.
|
||||
IEnumerable<Guid> keys = references.Select(x => x.Udi).OfType<GuidUdi>().Select(x => x.Guid);
|
||||
var keysLookup = scope.Database.FetchByGroups<NodeIdKey, Guid>(keys, Constants.Sql.MaxParameterCount, guids =>
|
||||
{
|
||||
return scope.SqlContext.Sql()
|
||||
.Select<NodeDto>(x => x.NodeId, x => x.UniqueId)
|
||||
.From<NodeDto>()
|
||||
.WhereIn<NodeDto>(x => x.UniqueId, guids);
|
||||
}).ToDictionary(x => x.UniqueId, x => x.NodeId);
|
||||
|
||||
// Get all valid relations.
|
||||
var relations = new List<(int ChildId, int RelationTypeId)>(references.Count);
|
||||
foreach (UmbracoEntityReference reference in references)
|
||||
{
|
||||
if (string.IsNullOrEmpty(reference.RelationTypeAlias))
|
||||
{
|
||||
// Reference does not specify a relation type alias, so skip adding a relation.
|
||||
_logger.LogDebug("The reference to {Udi} does not specify a relation type alias, so it will not be saved as relation.", reference.Udi);
|
||||
}
|
||||
else if (!automaticRelationTypeAliases.Contains(reference.RelationTypeAlias))
|
||||
{
|
||||
// Returning a reference that doesn't use an automatic relation type is an issue that should be fixed in code.
|
||||
_logger.LogError("The reference to {Udi} uses a relation type {RelationTypeAlias} that is not an automatic relation type.", reference.Udi, reference.RelationTypeAlias);
|
||||
}
|
||||
else if (!relationTypeLookup.TryGetValue(reference.RelationTypeAlias, out int relationTypeId))
|
||||
{
|
||||
// A non-existent relation type could be caused by an environment issue (e.g. it was manually removed).
|
||||
_logger.LogWarning("The reference to {Udi} uses a relation type {RelationTypeAlias} that does not exist.", reference.Udi, reference.RelationTypeAlias);
|
||||
}
|
||||
else if (reference.Udi is not GuidUdi udi || !keysLookup.TryGetValue(udi.Guid, out var id))
|
||||
{
|
||||
// Relations only support references to items that are stored in the NodeDto table (because of foreign key constraints).
|
||||
_logger.LogInformation("The reference to {Udi} can not be saved as relation, because it doesn't have a node ID.", reference.Udi);
|
||||
}
|
||||
else
|
||||
{
|
||||
relations.Add((id, relationTypeId));
|
||||
}
|
||||
}
|
||||
|
||||
// Get all existing relations (optimize for adding new and keeping existing relations).
|
||||
IQuery<IRelation> query = scope.SqlContext.Query<IRelation>().Where(x => x.ParentId == entity.Id).WhereIn(x => x.RelationTypeId, relationTypeLookup.Values);
|
||||
var existingRelations = _relationRepository.GetPagedRelationsByQuery(query, 0, int.MaxValue, out _, null)
|
||||
.ToDictionary(x => (x.ChildId, x.RelationTypeId)); // Relations are unique by parent ID, child ID and relation type ID.
|
||||
|
||||
// Add relations that don't exist yet.
|
||||
IEnumerable<ReadOnlyRelation> relationsToAdd = relations.Except(existingRelations.Keys).Select(x => new ReadOnlyRelation(entity.Id, x.ChildId, x.RelationTypeId));
|
||||
_relationRepository.SaveBulk(relationsToAdd);
|
||||
|
||||
// Delete relations that don't exist anymore.
|
||||
foreach (IRelation relation in existingRelations.Where(x => !relations.Contains(x.Key)).Select(x => x.Value))
|
||||
{
|
||||
_relationRepository.Delete(relation);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NodeIdKey
|
||||
{
|
||||
[Column("id")]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("uniqueId")]
|
||||
public Guid UniqueId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Persistence;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
@@ -1080,81 +1079,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
|
||||
#endregion
|
||||
|
||||
[Obsolete("This method is no longer used as the persistance of relations has been moved to the ContentRelationsUpdate notification handler. Scheduled for removal in Umbraco 18.")]
|
||||
protected void PersistRelations(TEntity entity)
|
||||
{
|
||||
// Get all references and automatic relation type aliases
|
||||
ISet<UmbracoEntityReference> references = _dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors);
|
||||
ISet<string> automaticRelationTypeAliases = _dataValueReferenceFactories.GetAllAutomaticRelationTypesAliases(PropertyEditors);
|
||||
|
||||
if (references.Count == 0)
|
||||
{
|
||||
// Delete all relations using the automatic relation type aliases
|
||||
RelationRepository.DeleteByParent(entity.Id, automaticRelationTypeAliases.ToArray());
|
||||
|
||||
// No need to add new references/relations
|
||||
return;
|
||||
}
|
||||
|
||||
// Lookup all relation type IDs
|
||||
var relationTypeLookup = RelationTypeRepository.GetMany(Array.Empty<int>())
|
||||
.Where(x => automaticRelationTypeAliases.Contains(x.Alias))
|
||||
.ToDictionary(x => x.Alias, x => x.Id);
|
||||
|
||||
// Lookup node IDs for all GUID based UDIs
|
||||
IEnumerable<Guid> keys = references.Select(x => x.Udi).OfType<GuidUdi>().Select(x => x.Guid);
|
||||
var keysLookup = Database.FetchByGroups<NodeIdKey, Guid>(keys, Constants.Sql.MaxParameterCount, guids =>
|
||||
{
|
||||
return Sql()
|
||||
.Select<NodeDto>(x => x.NodeId, x => x.UniqueId)
|
||||
.From<NodeDto>()
|
||||
.WhereIn<NodeDto>(x => x.UniqueId, guids);
|
||||
}).ToDictionary(x => x.UniqueId, x => x.NodeId);
|
||||
|
||||
// Get all valid relations
|
||||
var relations = new List<(int ChildId, int RelationTypeId)>(references.Count);
|
||||
foreach (UmbracoEntityReference reference in references)
|
||||
{
|
||||
if (string.IsNullOrEmpty(reference.RelationTypeAlias))
|
||||
{
|
||||
// Reference does not specify a relation type alias, so skip adding a relation
|
||||
Logger.LogDebug("The reference to {Udi} does not specify a relation type alias, so it will not be saved as relation.", reference.Udi);
|
||||
}
|
||||
else if (!automaticRelationTypeAliases.Contains(reference.RelationTypeAlias))
|
||||
{
|
||||
// Returning a reference that doesn't use an automatic relation type is an issue that should be fixed in code
|
||||
Logger.LogError("The reference to {Udi} uses a relation type {RelationTypeAlias} that is not an automatic relation type.", reference.Udi, reference.RelationTypeAlias);
|
||||
}
|
||||
else if (!relationTypeLookup.TryGetValue(reference.RelationTypeAlias, out int relationTypeId))
|
||||
{
|
||||
// A non-existent relation type could be caused by an environment issue (e.g. it was manually removed)
|
||||
Logger.LogWarning("The reference to {Udi} uses a relation type {RelationTypeAlias} that does not exist.", reference.Udi, reference.RelationTypeAlias);
|
||||
}
|
||||
else if (reference.Udi is not GuidUdi udi || !keysLookup.TryGetValue(udi.Guid, out var id))
|
||||
{
|
||||
// Relations only support references to items that are stored in the NodeDto table (because of foreign key constraints)
|
||||
Logger.LogInformation("The reference to {Udi} can not be saved as relation, because doesn't have a node ID.", reference.Udi);
|
||||
}
|
||||
else
|
||||
{
|
||||
relations.Add((id, relationTypeId));
|
||||
}
|
||||
}
|
||||
|
||||
// Get all existing relations (optimize for adding new and keeping existing relations)
|
||||
var query = Query<IRelation>().Where(x => x.ParentId == entity.Id).WhereIn(x => x.RelationTypeId, relationTypeLookup.Values);
|
||||
var existingRelations = RelationRepository.GetPagedRelationsByQuery(query, 0, int.MaxValue, out _, null)
|
||||
.ToDictionary(x => (x.ChildId, x.RelationTypeId)); // Relations are unique by parent ID, child ID and relation type ID
|
||||
|
||||
// Add relations that don't exist yet
|
||||
var relationsToAdd = relations.Except(existingRelations.Keys).Select(x => new ReadOnlyRelation(entity.Id, x.ChildId, x.RelationTypeId));
|
||||
RelationRepository.SaveBulk(relationsToAdd);
|
||||
|
||||
// Delete relations that don't exist anymore
|
||||
foreach (IRelation relation in existingRelations.Where(x => !relations.Contains(x.Key)).Select(x => x.Value))
|
||||
{
|
||||
RelationRepository.Delete(relation);
|
||||
}
|
||||
}
|
||||
=> Logger.LogWarning("ContentRepositoryBase.PersistRelations was called but this is now an obsolete, no-op method that is unused in Umbraco. No relations were persisted. Relations persistence has moved to the ContentRelationsUpdate notification handler.");
|
||||
|
||||
/// <summary>
|
||||
/// Inserts property values for the content entity
|
||||
@@ -1230,14 +1157,5 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
Database.Execute(SqlContext.Sql().Delete<PropertyDataDto>().WhereIn<PropertyDataDto>(x => x.Id, existingPropDataIds));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NodeIdKey
|
||||
{
|
||||
[Column("id")]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("uniqueId")]
|
||||
public Guid UniqueId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ internal sealed class DataTypeRepository : EntityRepositoryBase<int, IDataType>,
|
||||
private readonly ILogger<IDataType> _dataTypeLogger;
|
||||
private readonly PropertyEditorCollection _editors;
|
||||
private readonly IConfigurationEditorJsonSerializer _serializer;
|
||||
private readonly IDataValueEditorFactory _dataValueEditorFactory;
|
||||
|
||||
public DataTypeRepository(
|
||||
IScopeAccessor scopeAccessor,
|
||||
@@ -36,11 +37,13 @@ internal sealed class DataTypeRepository : EntityRepositoryBase<int, IDataType>,
|
||||
PropertyEditorCollection editors,
|
||||
ILogger<DataTypeRepository> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
IConfigurationEditorJsonSerializer serializer)
|
||||
IConfigurationEditorJsonSerializer serializer,
|
||||
IDataValueEditorFactory dataValueEditorFactory)
|
||||
: base(scopeAccessor, cache, logger)
|
||||
{
|
||||
_editors = editors;
|
||||
_serializer = serializer;
|
||||
_dataValueEditorFactory = dataValueEditorFactory;
|
||||
_dataTypeLogger = loggerFactory.CreateLogger<IDataType>();
|
||||
}
|
||||
|
||||
@@ -262,7 +265,12 @@ internal sealed class DataTypeRepository : EntityRepositoryBase<int, IDataType>,
|
||||
}
|
||||
|
||||
List<DataTypeDto>? dtos = Database.Fetch<DataTypeDto>(dataTypeSql);
|
||||
return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors, _dataTypeLogger, _serializer)).ToArray();
|
||||
return dtos.Select(x => DataTypeFactory.BuildEntity(
|
||||
x,
|
||||
_editors,
|
||||
_dataTypeLogger,
|
||||
_serializer,
|
||||
_dataValueEditorFactory)).ToArray();
|
||||
}
|
||||
|
||||
protected override IEnumerable<IDataType> PerformGetByQuery(IQuery<IDataType> query)
|
||||
@@ -273,7 +281,12 @@ internal sealed class DataTypeRepository : EntityRepositoryBase<int, IDataType>,
|
||||
|
||||
List<DataTypeDto>? dtos = Database.Fetch<DataTypeDto>(sql);
|
||||
|
||||
return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors, _dataTypeLogger, _serializer)).ToArray();
|
||||
return dtos.Select(x => DataTypeFactory.BuildEntity(
|
||||
x,
|
||||
_editors,
|
||||
_dataTypeLogger,
|
||||
_serializer,
|
||||
_dataValueEditorFactory)).ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1073,8 +1073,6 @@ public class DocumentRepository : ContentRepositoryBase<int, IContent, DocumentR
|
||||
ClearEntityTags(entity, _tagRepository);
|
||||
}
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
// troubleshooting
|
||||
@@ -1324,8 +1322,6 @@ public class DocumentRepository : ContentRepositoryBase<int, IContent, DocumentR
|
||||
ClearEntityTags(entity, _tagRepository);
|
||||
}
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
// TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what?
|
||||
}
|
||||
|
||||
@@ -1671,24 +1667,30 @@ public class DocumentRepository : ContentRepositoryBase<int, IContent, DocumentR
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Guid> GetScheduledContentKeys(Guid[] keys)
|
||||
public IDictionary<int, IEnumerable<ContentSchedule>> GetContentSchedulesByIds(int[] documentIds)
|
||||
{
|
||||
var action = ContentScheduleAction.Release.ToString();
|
||||
DateTime now = DateTime.UtcNow;
|
||||
Sql<ISqlContext> sql = Sql()
|
||||
.Select<ContentScheduleDto>()
|
||||
.From<ContentScheduleDto>()
|
||||
.WhereIn<ContentScheduleDto>(contentScheduleDto => contentScheduleDto.NodeId, documentIds);
|
||||
|
||||
Sql<ISqlContext> sql = SqlContext.Sql();
|
||||
sql
|
||||
.Select<NodeDto>(x => x.UniqueId)
|
||||
.From<DocumentDto>()
|
||||
.InnerJoin<ContentDto>().On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<NodeDto>().On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
|
||||
.WhereIn<NodeDto>(x => x.UniqueId, keys)
|
||||
.WhereIn<NodeDto>(x => x.NodeId, Sql()
|
||||
.Select<ContentScheduleDto>(x => x.NodeId)
|
||||
.From<ContentScheduleDto>()
|
||||
.Where<ContentScheduleDto>(x => x.Action == action && x.Date >= now));
|
||||
List<ContentScheduleDto>? contentScheduleDtos = Database.Fetch<ContentScheduleDto>(sql);
|
||||
|
||||
return Database.Fetch<Guid>(sql);
|
||||
IDictionary<int, IEnumerable<ContentSchedule>> dictionary = contentScheduleDtos
|
||||
.GroupBy(contentSchedule => contentSchedule.NodeId)
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.Select(scheduleDto => new ContentSchedule(
|
||||
scheduleDto.Id,
|
||||
LanguageRepository.GetIsoCodeById(scheduleDto.LanguageId) ?? Constants.System.InvariantCulture,
|
||||
scheduleDto.Date,
|
||||
scheduleDto.Action == ContentScheduleAction.Release.ToString()
|
||||
? ContentScheduleAction.Release
|
||||
: ContentScheduleAction.Expire))
|
||||
.ToList().AsEnumerable()); // We have to materialize it here,
|
||||
// to avoid this being used after the scope is disposed.
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -416,8 +416,6 @@ public class MediaRepository : ContentRepositoryBase<int, IMedia, MediaRepositor
|
||||
// set tags
|
||||
SetEntityTags(entity, _tagRepository, _serializer);
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
OnUowRefreshedEntity(new MediaRefreshNotification(entity, new EventMessages()));
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
@@ -477,8 +475,6 @@ public class MediaRepository : ContentRepositoryBase<int, IMedia, MediaRepositor
|
||||
ReplacePropertyValues(entity, entity.VersionId, 0, out _, out _);
|
||||
|
||||
SetEntityTags(entity, _tagRepository, _serializer);
|
||||
|
||||
PersistRelations(entity);
|
||||
}
|
||||
|
||||
OnUowRefreshedEntity(new MediaRefreshNotification(entity, new EventMessages()));
|
||||
|
||||
@@ -788,8 +788,6 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
|
||||
|
||||
SetEntityTags(entity, _tagRepository, _jsonSerializer);
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
OnUowRefreshedEntity(new MemberRefreshNotification(entity, new EventMessages()));
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
@@ -938,8 +936,6 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
|
||||
|
||||
SetEntityTags(entity, _tagRepository, _jsonSerializer);
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
OnUowRefreshedEntity(new MemberRefreshNotification(entity, new EventMessages()));
|
||||
|
||||
_memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, entity.Username);
|
||||
|
||||
@@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.PropertyEditors;
|
||||
using Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -61,6 +62,9 @@ internal sealed class FileUploadPropertyValueEditor : DataValueEditor
|
||||
IsAllowedInDataTypeConfiguration));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IValueRequiredValidator RequiredValidator => new FileUploadValueRequiredValidator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? ToEditor(IProperty property, string? culture = null, string? segment = null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Nodes;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Custom validator for block value required validation.
|
||||
/// </summary>
|
||||
internal sealed class FileUploadValueRequiredValidator : RequiredValidator
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType)
|
||||
{
|
||||
IEnumerable<ValidationResult> validationResults = base.ValidateRequired(value, valueType);
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
if (value is JsonObject jsonObject && jsonObject.TryGetPropertyValue("src", out JsonNode? source))
|
||||
{
|
||||
string sourceString = source!.GetValue<string>();
|
||||
if (string.IsNullOrEmpty(sourceString))
|
||||
{
|
||||
validationResults = validationResults.Append(new ValidationResult(Constants.Validation.ErrorMessages.Properties.Empty, ["value"]));
|
||||
}
|
||||
}
|
||||
|
||||
return validationResults;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user