* Add support for programmatic creation of property types providing the data type key (#19720) * Add support for programmatic creation of property types providing the data type key. * Add integration tests --------- Co-authored-by: kjac <kja@umbraco.dk> * Don't use Lazy --------- Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Querying;
|
||||
@@ -25,8 +26,9 @@ internal class ContentTypeRepository : ContentTypeRepositoryBase<IContentType>,
|
||||
ILogger<ContentTypeRepository> logger,
|
||||
IContentTypeCommonRepository commonRepository,
|
||||
ILanguageRepository languageRepository,
|
||||
IShortStringHelper shortStringHelper)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper)
|
||||
IShortStringHelper shortStringHelper,
|
||||
IIdKeyMap idKeyMap)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper, idKeyMap)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -29,15 +29,22 @@ internal abstract class ContentTypeRepositoryBase<TEntity> : EntityRepositoryBas
|
||||
where TEntity : class, IContentTypeComposition
|
||||
{
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
|
||||
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache,
|
||||
ILogger<ContentTypeRepositoryBase<TEntity>> logger, IContentTypeCommonRepository commonRepository,
|
||||
ILanguageRepository languageRepository, IShortStringHelper shortStringHelper)
|
||||
protected ContentTypeRepositoryBase(
|
||||
IScopeAccessor scopeAccessor,
|
||||
AppCaches cache,
|
||||
ILogger<ContentTypeRepositoryBase<TEntity>> logger,
|
||||
IContentTypeCommonRepository commonRepository,
|
||||
ILanguageRepository languageRepository,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IIdKeyMap idKeyMap)
|
||||
: base(scopeAccessor, cache, logger)
|
||||
{
|
||||
_shortStringHelper = shortStringHelper;
|
||||
CommonRepository = commonRepository;
|
||||
LanguageRepository = languageRepository;
|
||||
_idKeyMap = idKeyMap;
|
||||
}
|
||||
|
||||
protected IContentTypeCommonRepository CommonRepository { get; }
|
||||
@@ -291,7 +298,7 @@ AND umbracoNode.nodeObjectType = @objectType",
|
||||
// If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias
|
||||
if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default)
|
||||
{
|
||||
AssignDataTypeFromPropertyEditor(propertyType);
|
||||
AssignDataTypeIdFromProvidedKeyOrPropertyEditor(propertyType);
|
||||
}
|
||||
|
||||
PropertyTypeDto propertyTypeDto =
|
||||
@@ -592,7 +599,7 @@ AND umbracoNode.id <> @id",
|
||||
// if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias
|
||||
if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default)
|
||||
{
|
||||
AssignDataTypeFromPropertyEditor(propertyType);
|
||||
AssignDataTypeIdFromProvidedKeyOrPropertyEditor(propertyType);
|
||||
}
|
||||
|
||||
// validate the alias
|
||||
@@ -1434,37 +1441,59 @@ AND umbracoNode.id <> @id",
|
||||
protected abstract TEntity? PerformGet(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Try to set the data type id based on its ControlId
|
||||
/// Try to set the data type Id based on the provided key or property editor alias.
|
||||
/// </summary>
|
||||
/// <param name="propertyType"></param>
|
||||
private void AssignDataTypeFromPropertyEditor(IPropertyType propertyType)
|
||||
private void AssignDataTypeIdFromProvidedKeyOrPropertyEditor(IPropertyType propertyType)
|
||||
{
|
||||
// we cannot try to assign a data type of it's empty
|
||||
if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false)
|
||||
// If a key is provided, use that.
|
||||
if (propertyType.DataTypeKey != Guid.Empty)
|
||||
{
|
||||
Sql<ISqlContext> sql = Sql()
|
||||
.Select<DataTypeDto>(dt => dt.Select(x => x.NodeDto))
|
||||
.From<DataTypeDto>()
|
||||
.InnerJoin<NodeDto>().On<DataTypeDto, NodeDto>((dt, n) => dt.NodeId == n.NodeId)
|
||||
.Where(
|
||||
"propertyEditorAlias = @propertyEditorAlias",
|
||||
new { propertyEditorAlias = propertyType.PropertyEditorAlias })
|
||||
.OrderBy<DataTypeDto>(typeDto => typeDto.NodeId);
|
||||
DataTypeDto? datatype = Database.FirstOrDefault<DataTypeDto>(sql);
|
||||
|
||||
// we cannot assign a data type if one was not found
|
||||
if (datatype != null)
|
||||
Attempt<int> dataTypeIdAttempt = _idKeyMap.GetIdForKey(propertyType.DataTypeKey, UmbracoObjectTypes.DataType);
|
||||
if (dataTypeIdAttempt.Success)
|
||||
{
|
||||
propertyType.DataTypeId = datatype.NodeId;
|
||||
propertyType.DataTypeKey = datatype.NodeDto.UniqueId;
|
||||
propertyType.DataTypeId = dataTypeIdAttempt.Result;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning(
|
||||
"Could not assign a data type for the property type {PropertyTypeAlias} since no data type was found with a property editor {PropertyEditorAlias}",
|
||||
propertyType.Alias, propertyType.PropertyEditorAlias);
|
||||
"Could not assign a data type for the property type {PropertyTypeAlias} since no integer Id was found matching the key {DataTypeKey}. Falling back to look up via the property editor alias.",
|
||||
propertyType.Alias,
|
||||
propertyType.DataTypeKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise if a property editor alias is provided, try to find a data type that uses that alias.
|
||||
if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace())
|
||||
{
|
||||
// We cannot try to assign a data type if it's empty.
|
||||
return;
|
||||
}
|
||||
|
||||
Sql<ISqlContext> sql = Sql()
|
||||
.Select<DataTypeDto>(dt => dt.Select(x => x.NodeDto))
|
||||
.From<DataTypeDto>()
|
||||
.InnerJoin<NodeDto>().On<DataTypeDto, NodeDto>((dt, n) => dt.NodeId == n.NodeId)
|
||||
.Where(
|
||||
"propertyEditorAlias = @propertyEditorAlias",
|
||||
new { propertyEditorAlias = propertyType.PropertyEditorAlias })
|
||||
.OrderBy<DataTypeDto>(typeDto => typeDto.NodeId);
|
||||
DataTypeDto? datatype = Database.FirstOrDefault<DataTypeDto>(sql);
|
||||
|
||||
// we cannot assign a data type if one was not found
|
||||
if (datatype != null)
|
||||
{
|
||||
propertyType.DataTypeId = datatype.NodeId;
|
||||
propertyType.DataTypeKey = datatype.NodeDto.UniqueId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning(
|
||||
"Could not assign a data type for the property type {PropertyTypeAlias} since no data type was found with a property editor {PropertyEditorAlias}",
|
||||
propertyType.Alias,
|
||||
propertyType.PropertyEditorAlias);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract TEntity? PerformGet(string alias);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Querying;
|
||||
@@ -24,8 +25,9 @@ internal class MediaTypeRepository : ContentTypeRepositoryBase<IMediaType>, IMed
|
||||
ILogger<MediaTypeRepository> logger,
|
||||
IContentTypeCommonRepository commonRepository,
|
||||
ILanguageRepository languageRepository,
|
||||
IShortStringHelper shortStringHelper)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper)
|
||||
IShortStringHelper shortStringHelper,
|
||||
IIdKeyMap idKeyMap)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper, idKeyMap)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Factories;
|
||||
@@ -27,9 +28,10 @@ internal class MemberTypeRepository : ContentTypeRepositoryBase<IMemberType>, IM
|
||||
ILogger<MemberTypeRepository> logger,
|
||||
IContentTypeCommonRepository commonRepository,
|
||||
ILanguageRepository languageRepository,
|
||||
IShortStringHelper shortStringHelper)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper) =>
|
||||
_shortStringHelper = shortStringHelper;
|
||||
IShortStringHelper shortStringHelper,
|
||||
IIdKeyMap idKeyMap)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper, idKeyMap)
|
||||
=> _shortStringHelper = shortStringHelper;
|
||||
|
||||
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
|
||||
|
||||
|
||||
@@ -60,6 +60,8 @@ public abstract class UmbracoIntegrationTest : UmbracoIntegrationTestBase
|
||||
|
||||
protected IShortStringHelper ShortStringHelper => Services.GetRequiredService<IShortStringHelper>();
|
||||
|
||||
protected IIdKeyMap IdKeyMap => Services.GetRequiredService<IIdKeyMap>();
|
||||
|
||||
protected GlobalSettings GlobalSettings => Services.GetRequiredService<IOptions<GlobalSettings>>().Value;
|
||||
|
||||
protected IMapperCollection Mappers => Services.GetRequiredService<IMapperCollection>();
|
||||
|
||||
@@ -123,7 +123,7 @@ internal sealed class DocumentRepositoryTest : UmbracoIntegrationTest
|
||||
new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches, ShortStringHelper);
|
||||
var languageRepository =
|
||||
new LanguageRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<LanguageRepository>());
|
||||
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<ContentTypeRepository>(), commonRepository, languageRepository, ShortStringHelper);
|
||||
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<ContentTypeRepository>(), commonRepository, languageRepository, ShortStringHelper, IdKeyMap);
|
||||
var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger<RelationTypeRepository>());
|
||||
var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled);
|
||||
var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger<RelationRepository>(), relationTypeRepository, entityRepository);
|
||||
|
||||
@@ -56,7 +56,7 @@ internal sealed class MediaRepositoryTest : UmbracoIntegrationTest
|
||||
new ContentTypeCommonRepository(scopeAccessor, TemplateRepository, appCaches, ShortStringHelper);
|
||||
var languageRepository =
|
||||
new LanguageRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<LanguageRepository>());
|
||||
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<MediaTypeRepository>(), commonRepository, languageRepository, ShortStringHelper);
|
||||
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<MediaTypeRepository>(), commonRepository, languageRepository, ShortStringHelper, IdKeyMap);
|
||||
var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<TagRepository>());
|
||||
var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger<RelationTypeRepository>());
|
||||
var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled);
|
||||
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
@@ -411,7 +412,7 @@ internal sealed class MediaTypeRepositoryTest : UmbracoIntegrationTest
|
||||
}
|
||||
|
||||
private MediaTypeRepository CreateRepository(IScopeProvider provider) =>
|
||||
new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger<MediaTypeRepository>(), CommonRepository, LanguageRepository, ShortStringHelper);
|
||||
new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger<MediaTypeRepository>(), CommonRepository, LanguageRepository, ShortStringHelper, IdKeyMap);
|
||||
|
||||
private EntityContainerRepository CreateContainerRepository(IScopeProvider provider) =>
|
||||
new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger<EntityContainerRepository>(), Constants.ObjectTypes.MediaTypeContainer);
|
||||
|
||||
@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
@@ -26,7 +27,7 @@ internal sealed class MemberTypeRepositoryTest : UmbracoIntegrationTest
|
||||
{
|
||||
var commonRepository = GetRequiredService<IContentTypeCommonRepository>();
|
||||
var languageRepository = GetRequiredService<ILanguageRepository>();
|
||||
return new MemberTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of<ILogger<MemberTypeRepository>>(), commonRepository, languageRepository, ShortStringHelper);
|
||||
return new MemberTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of<ILogger<MemberTypeRepository>>(), commonRepository, languageRepository, ShortStringHelper, IdKeyMap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -266,7 +266,7 @@ internal sealed class TemplateRepositoryTest : UmbracoIntegrationTest
|
||||
var commonRepository =
|
||||
new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches, ShortStringHelper);
|
||||
var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger<LanguageRepository>());
|
||||
var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger<ContentTypeRepository>(), commonRepository, languageRepository, ShortStringHelper);
|
||||
var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger<ContentTypeRepository>(), commonRepository, languageRepository, ShortStringHelper, IdKeyMap);
|
||||
var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger<RelationTypeRepository>());
|
||||
var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled);
|
||||
var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger<RelationRepository>(), relationTypeRepository, entityRepository);
|
||||
|
||||
@@ -1971,6 +1971,65 @@ internal sealed class ContentTypeServiceTests : UmbracoIntegrationTest
|
||||
.Variations);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Create_Property_Type_Based_On_DataTypeKey()
|
||||
{
|
||||
// Arrange
|
||||
var cts = ContentTypeService;
|
||||
var dtdYesNo = DataTypeService.GetDataType(-49);
|
||||
IContentType ctBase = new ContentType(ShortStringHelper, -1)
|
||||
{
|
||||
Name = "Base",
|
||||
Alias = "Base",
|
||||
Icon = "folder.gif",
|
||||
Thumbnail = "folder.png"
|
||||
};
|
||||
ctBase.AddPropertyType(new PropertyType(ShortStringHelper, "ShouldNotMatter", ValueStorageType.Nvarchar)
|
||||
{
|
||||
Name = "Hide From Navigation",
|
||||
Alias = Constants.Conventions.Content.NaviHide,
|
||||
DataTypeKey = dtdYesNo.Key
|
||||
});
|
||||
cts.Save(ctBase);
|
||||
|
||||
// Assert
|
||||
ctBase = cts.Get(ctBase.Key);
|
||||
Assert.That(ctBase, Is.Not.Null);
|
||||
Assert.That(ctBase.HasIdentity, Is.True);
|
||||
Assert.That(ctBase.PropertyTypes.Count(), Is.EqualTo(1));
|
||||
Assert.That(ctBase.PropertyTypes.First().DataTypeId, Is.EqualTo(dtdYesNo.Id));
|
||||
Assert.That(ctBase.PropertyTypes.First().PropertyEditorAlias, Is.EqualTo(dtdYesNo.EditorAlias));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Create_Property_Type_Based_On_PropertyEditorAlias()
|
||||
{
|
||||
// Arrange
|
||||
var cts = ContentTypeService;
|
||||
var dtdYesNo = DataTypeService.GetDataType(-49);
|
||||
IContentType ctBase = new ContentType(ShortStringHelper, -1)
|
||||
{
|
||||
Name = "Base",
|
||||
Alias = "Base",
|
||||
Icon = "folder.gif",
|
||||
Thumbnail = "folder.png"
|
||||
};
|
||||
ctBase.AddPropertyType(new PropertyType(ShortStringHelper, "Umbraco.TrueFalse", ValueStorageType.Nvarchar)
|
||||
{
|
||||
Name = "Hide From Navigation",
|
||||
Alias = Constants.Conventions.Content.NaviHide,
|
||||
});
|
||||
cts.Save(ctBase);
|
||||
|
||||
// Assert
|
||||
ctBase = cts.Get(ctBase.Key);
|
||||
Assert.That(ctBase, Is.Not.Null);
|
||||
Assert.That(ctBase.HasIdentity, Is.True);
|
||||
Assert.That(ctBase.PropertyTypes.Count(), Is.EqualTo(1));
|
||||
Assert.That(ctBase.PropertyTypes.First().DataTypeId, Is.EqualTo(dtdYesNo.Id));
|
||||
Assert.That(ctBase.PropertyTypes.First().PropertyEditorAlias, Is.EqualTo(dtdYesNo.EditorAlias));
|
||||
}
|
||||
|
||||
private ContentType CreateComponent()
|
||||
{
|
||||
var component = new ContentType(ShortStringHelper, -1)
|
||||
|
||||
Reference in New Issue
Block a user