Member types: Implement containers (#20706)
* Add MemberType/MemberTypeContainer to supported EntityContainer object types * Implement MemberTypeContainerRepository * Update and add member type container API endpoints * Complete server and client-side implementation for member type container support. * Fix FE linting errors. * Export folder constants. * Applied suggestions from code review. * Updated management API authorization tests for member types. * Resolved breaking change on copy member type controller. * Allow content types to be moved to own folder without error. * Use flag providers for member type siblings endpoint. --------- Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
@@ -430,6 +430,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
Services.AddUnique<ITemporaryFileToXmlImportService, TemporaryFileToXmlImportService>();
|
||||
Services.AddUnique<IContentTypeImportService, ContentTypeImportService>();
|
||||
Services.AddUnique<IMediaTypeImportService, MediaTypeImportService>();
|
||||
Services.AddUnique<IMemberTypeImportService, MemberTypeImportService>();
|
||||
|
||||
// add validation services
|
||||
Services.AddUnique<IElementSwitchValidator, ElementSwitchValidator>();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
namespace Umbraco.Cms.Core.Models.ContentTypeEditing;
|
||||
namespace Umbraco.Cms.Core.Models.ContentTypeEditing;
|
||||
|
||||
public class MemberTypeCreateModel : MemberTypeModelBase
|
||||
{
|
||||
public Guid? Key { get; set; }
|
||||
|
||||
public Guid? ContainerKey { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ internal sealed class MemberTypeEditingService : ContentTypeEditingServiceBase<I
|
||||
|
||||
public async Task<Attempt<IMemberType?, ContentTypeOperationStatus>> CreateAsync(MemberTypeCreateModel model, Guid userKey)
|
||||
{
|
||||
Attempt<IMemberType?, ContentTypeOperationStatus> result = await ValidateAndMapForCreationAsync(model, model.Key, containerKey: null);
|
||||
Attempt<IMemberType?, ContentTypeOperationStatus> result = await ValidateAndMapForCreationAsync(model, model.Key, model.ContainerKey);
|
||||
if (result.Success is false)
|
||||
{
|
||||
return result;
|
||||
@@ -80,7 +80,7 @@ internal sealed class MemberTypeEditingService : ContentTypeEditingServiceBase<I
|
||||
|
||||
protected override UmbracoObjectTypes ContentTypeObjectType => UmbracoObjectTypes.MemberType;
|
||||
|
||||
protected override UmbracoObjectTypes ContainerObjectType => throw new NotSupportedException("Member type tree does not support containers");
|
||||
protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.MemberTypeContainer;
|
||||
|
||||
protected override ISet<string> GetReservedFieldNames() => _reservedFieldNamesService.GetMemberReservedFieldNames();
|
||||
|
||||
|
||||
@@ -1053,9 +1053,9 @@ public abstract class ContentTypeServiceBase<TRepository, TItem> : ContentTypeSe
|
||||
public Attempt<OperationResult<MoveOperationStatusType>?> Move(TItem moving, int containerId)
|
||||
{
|
||||
EventMessages eventMessages = EventMessagesFactory.Get();
|
||||
if(moving.ParentId == containerId)
|
||||
if (moving.ParentId == containerId)
|
||||
{
|
||||
return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedNotAllowedByPath, eventMessages);
|
||||
return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages);
|
||||
}
|
||||
|
||||
var moveInfo = new List<MoveEventInfo<TItem>>();
|
||||
|
||||
@@ -374,7 +374,11 @@ internal sealed class EntityXmlSerializer : IEntityXmlSerializer
|
||||
return xml;
|
||||
}
|
||||
|
||||
public XElement Serialize(IMediaType mediaType)
|
||||
public XElement Serialize(IMediaType mediaType) => SerializeMediaOrMemberType(mediaType, IEntityXmlSerializer.MediaTypeElementName);
|
||||
|
||||
public XElement Serialize(IMemberType memberType) => SerializeMediaOrMemberType(memberType, IEntityXmlSerializer.MemberTypeElementName);
|
||||
|
||||
private XElement SerializeMediaOrMemberType(IContentTypeComposition mediaType, string elementName)
|
||||
{
|
||||
var info = new XElement(
|
||||
"Info",
|
||||
@@ -410,7 +414,7 @@ internal sealed class EntityXmlSerializer : IEntityXmlSerializer
|
||||
SerializePropertyGroups(mediaType.PropertyGroups)); // TODO Rename to PropertyGroups
|
||||
|
||||
var xml = new XElement(
|
||||
IEntityXmlSerializer.MediaTypeElementName,
|
||||
elementName,
|
||||
info,
|
||||
structure,
|
||||
genericProperties,
|
||||
|
||||
@@ -10,6 +10,7 @@ public interface IEntityXmlSerializer
|
||||
{
|
||||
internal const string DocumentTypeElementName = "DocumentType";
|
||||
internal const string MediaTypeElementName = "MediaType";
|
||||
internal const string MemberTypeElementName = "MemberType";
|
||||
|
||||
/// <summary>
|
||||
/// Exports an IContent item as an XElement.
|
||||
@@ -80,5 +81,7 @@ public interface IEntityXmlSerializer
|
||||
|
||||
XElement Serialize(IMediaType mediaType);
|
||||
|
||||
XElement Serialize(IMemberType memberType) => throw new NotImplementedException();
|
||||
|
||||
XElement Serialize(IContentType contentType);
|
||||
}
|
||||
|
||||
@@ -10,13 +10,21 @@ public interface IPackageDataInstallation
|
||||
InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves package xml as <see cref="IContentType"/>
|
||||
/// Imports and saves package xml as <see cref="IMediaType"/>.
|
||||
/// </summary>
|
||||
/// <param name="docTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated ContentTypes</returns>
|
||||
/// <returns>An enumerable list of generated <see cref="IMediaType"/>s.</returns>
|
||||
IReadOnlyList<IMediaType> ImportMediaTypes(IEnumerable<XElement> docTypeElements, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves package xml as <see cref="IMemberType"/>.
|
||||
/// </summary>
|
||||
/// <param name="docTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated <see cref="IMemberType"/>s.</returns>
|
||||
IReadOnlyList<IMemberType> ImportMemberTypes(IEnumerable<XElement> docTypeElements, int userId) => throw new NotImplementedException();
|
||||
|
||||
IReadOnlyList<TContentBase> ImportContentBase<TContentBase, TContentTypeComposition>(
|
||||
IEnumerable<CompiledPackageContentBase> docs,
|
||||
IDictionary<string, TContentTypeComposition> importedDocumentTypes,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services.ImportExport;
|
||||
|
||||
public interface IMemberTypeImportService
|
||||
{
|
||||
Task<Attempt<IMemberType?, MemberTypeImportOperationStatus>> Import(
|
||||
Guid temporaryFileId,
|
||||
Guid userKey,
|
||||
Guid? mediaTypeId = null);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services.ImportExport;
|
||||
|
||||
public class MemberTypeImportService : IMemberTypeImportService
|
||||
{
|
||||
private readonly IPackageDataInstallation _packageDataInstallation;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly ITemporaryFileToXmlImportService _temporaryFileToXmlImportService;
|
||||
private readonly ICoreScopeProvider _coreScopeProvider;
|
||||
private readonly IUserIdKeyResolver _userIdKeyResolver;
|
||||
|
||||
public MemberTypeImportService(
|
||||
IPackageDataInstallation packageDataInstallation,
|
||||
IEntityService entityService,
|
||||
ITemporaryFileToXmlImportService temporaryFileToXmlImportService,
|
||||
ICoreScopeProvider coreScopeProvider,
|
||||
IUserIdKeyResolver userIdKeyResolver)
|
||||
{
|
||||
_packageDataInstallation = packageDataInstallation;
|
||||
_entityService = entityService;
|
||||
_temporaryFileToXmlImportService = temporaryFileToXmlImportService;
|
||||
_coreScopeProvider = coreScopeProvider;
|
||||
_userIdKeyResolver = userIdKeyResolver;
|
||||
}
|
||||
|
||||
public async Task<Attempt<IMemberType?, MemberTypeImportOperationStatus>> Import(
|
||||
Guid temporaryFileId,
|
||||
Guid userKey,
|
||||
Guid? memberTypeId = null)
|
||||
{
|
||||
using ICoreScope scope = _coreScopeProvider.CreateCoreScope();
|
||||
|
||||
_temporaryFileToXmlImportService.CleanupFileIfScopeCompletes(temporaryFileId);
|
||||
Attempt<XElement?, TemporaryFileXmlImportOperationStatus> loadXmlAttempt =
|
||||
await _temporaryFileToXmlImportService.LoadXElementFromTemporaryFileAsync(temporaryFileId);
|
||||
if (loadXmlAttempt.Success is false)
|
||||
{
|
||||
return Attempt.FailWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
|
||||
loadXmlAttempt.Status is TemporaryFileXmlImportOperationStatus.TemporaryFileNotFound
|
||||
? MemberTypeImportOperationStatus.TemporaryFileNotFound
|
||||
: MemberTypeImportOperationStatus.TemporaryFileConversionFailure,
|
||||
null);
|
||||
}
|
||||
|
||||
Attempt<UmbracoEntityTypes> packageEntityTypeAttempt = _temporaryFileToXmlImportService.GetEntityType(loadXmlAttempt.Result!);
|
||||
if (packageEntityTypeAttempt.Success is false ||
|
||||
packageEntityTypeAttempt.Result is not UmbracoEntityTypes.MemberType)
|
||||
{
|
||||
return Attempt.FailWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
|
||||
MemberTypeImportOperationStatus.TypeMismatch,
|
||||
null);
|
||||
}
|
||||
|
||||
Guid packageEntityKey = _packageDataInstallation.GetContentTypeKey(loadXmlAttempt.Result!);
|
||||
if (memberTypeId is not null && memberTypeId.Equals(packageEntityKey) is false)
|
||||
{
|
||||
return Attempt.FailWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
|
||||
MemberTypeImportOperationStatus.IdMismatch,
|
||||
null);
|
||||
}
|
||||
|
||||
var entityExits = _entityService.Exists(
|
||||
_packageDataInstallation.GetContentTypeKey(loadXmlAttempt.Result!),
|
||||
UmbracoObjectTypes.MemberType);
|
||||
if (entityExits && memberTypeId is null)
|
||||
{
|
||||
return Attempt.FailWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
|
||||
MemberTypeImportOperationStatus.MemberTypeExists,
|
||||
null);
|
||||
}
|
||||
|
||||
IReadOnlyList<IMemberType> importResult =
|
||||
_packageDataInstallation.ImportMemberTypes(new[] { loadXmlAttempt.Result! }, await _userIdKeyResolver.GetAsync(userKey));
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return Attempt.SucceedWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
|
||||
entityExits
|
||||
? MemberTypeImportOperationStatus.SuccessUpdated
|
||||
: MemberTypeImportOperationStatus.SuccessCreated,
|
||||
importResult[0]);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,8 @@ public class TemporaryFileToXmlImportService : ITemporaryFileToXmlImportService
|
||||
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.DocumentType),
|
||||
IEntityXmlSerializer.MediaTypeElementName
|
||||
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.MediaType),
|
||||
IEntityXmlSerializer.MemberTypeElementName
|
||||
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.MemberType),
|
||||
_ => Attempt<UmbracoEntityTypes>.Fail()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
public enum MemberTypeImportOperationStatus
|
||||
{
|
||||
SuccessCreated,
|
||||
SuccessUpdated,
|
||||
TemporaryFileNotFound,
|
||||
TemporaryFileConversionFailure,
|
||||
MemberTypeExists,
|
||||
TypeMismatch,
|
||||
IdMismatch
|
||||
}
|
||||
Reference in New Issue
Block a user