2023-02-20 11:08:22 +01:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Umbraco.Cms.Core.Models;
|
|
|
|
|
|
using Umbraco.Cms.Core.Models.ContentEditing;
|
|
|
|
|
|
using Umbraco.Cms.Core.PropertyEditors;
|
|
|
|
|
|
using Umbraco.Cms.Core.Scoping;
|
|
|
|
|
|
using Umbraco.Cms.Core.Services.OperationStatus;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Cms.Core.Services;
|
|
|
|
|
|
|
2023-04-14 09:44:52 +02:00
|
|
|
|
// FIXME: add granular permissions check (for inspiration, check how the old ContentController utilizes IAuthorizationService)
|
2023-02-20 11:08:22 +01:00
|
|
|
|
internal sealed class ContentEditingService
|
|
|
|
|
|
: ContentEditingServiceBase<IContent, IContentType, IContentService, IContentTypeService>, IContentEditingService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ITemplateService _templateService;
|
|
|
|
|
|
private readonly ILogger<ContentEditingService> _logger;
|
2023-03-29 08:14:47 +02:00
|
|
|
|
private readonly IUserIdKeyResolver _userIdKeyResolver;
|
2023-02-20 11:08:22 +01:00
|
|
|
|
|
|
|
|
|
|
public ContentEditingService(
|
|
|
|
|
|
IContentService contentService,
|
|
|
|
|
|
IContentTypeService contentTypeService,
|
|
|
|
|
|
PropertyEditorCollection propertyEditorCollection,
|
|
|
|
|
|
IDataTypeService dataTypeService,
|
|
|
|
|
|
ITemplateService templateService,
|
|
|
|
|
|
ILogger<ContentEditingService> logger,
|
2023-03-21 12:41:20 +01:00
|
|
|
|
ICoreScopeProvider scopeProvider,
|
2023-03-29 08:14:47 +02:00
|
|
|
|
IUserIdKeyResolver userIdKeyResolver)
|
2023-03-13 15:02:30 +01:00
|
|
|
|
: base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider)
|
2023-02-20 11:08:22 +01:00
|
|
|
|
{
|
|
|
|
|
|
_templateService = templateService;
|
|
|
|
|
|
_logger = logger;
|
2023-03-29 08:14:47 +02:00
|
|
|
|
_userIdKeyResolver = userIdKeyResolver;
|
2023-02-20 11:08:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-21 09:46:23 +01:00
|
|
|
|
public async Task<IContent?> GetAsync(Guid id)
|
|
|
|
|
|
{
|
|
|
|
|
|
IContent? content = ContentService.GetById(id);
|
|
|
|
|
|
return await Task.FromResult(content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-03-21 12:41:20 +01:00
|
|
|
|
public async Task<Attempt<IContent?, ContentEditingOperationStatus>> CreateAsync(ContentCreateModel createModel, Guid userKey)
|
2023-02-20 11:08:22 +01:00
|
|
|
|
{
|
|
|
|
|
|
Attempt<IContent?, ContentEditingOperationStatus> result = await MapCreate(createModel);
|
|
|
|
|
|
if (result.Success == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IContent content = result.Result!;
|
|
|
|
|
|
ContentEditingOperationStatus operationStatus = await UpdateTemplateAsync(content, createModel.TemplateKey);
|
|
|
|
|
|
if (operationStatus != ContentEditingOperationStatus.Success)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Attempt.FailWithStatus<IContent?, ContentEditingOperationStatus>(operationStatus, content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-14 09:44:52 +02:00
|
|
|
|
operationStatus = await Save(content, userKey);
|
2023-02-20 11:08:22 +01:00
|
|
|
|
return operationStatus == ContentEditingOperationStatus.Success
|
|
|
|
|
|
? Attempt.SucceedWithStatus<IContent?, ContentEditingOperationStatus>(ContentEditingOperationStatus.Success, content)
|
|
|
|
|
|
: Attempt.FailWithStatus<IContent?, ContentEditingOperationStatus>(operationStatus, content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-03-21 12:41:20 +01:00
|
|
|
|
public async Task<Attempt<IContent, ContentEditingOperationStatus>> UpdateAsync(IContent content, ContentUpdateModel updateModel, Guid userKey)
|
2023-02-20 11:08:22 +01:00
|
|
|
|
{
|
|
|
|
|
|
Attempt<ContentEditingOperationStatus> result = await MapUpdate(content, updateModel);
|
|
|
|
|
|
if (result.Success == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Attempt.FailWithStatus(result.Result, content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ContentEditingOperationStatus operationStatus = await UpdateTemplateAsync(content, updateModel.TemplateKey);
|
|
|
|
|
|
if (operationStatus != ContentEditingOperationStatus.Success)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Attempt.FailWithStatus(operationStatus, content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-14 09:44:52 +02:00
|
|
|
|
operationStatus = await Save(content, userKey);
|
2023-02-20 11:08:22 +01:00
|
|
|
|
return operationStatus == ContentEditingOperationStatus.Success
|
|
|
|
|
|
? Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, content)
|
|
|
|
|
|
: Attempt.FailWithStatus(operationStatus, content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-03-21 12:41:20 +01:00
|
|
|
|
public async Task<Attempt<IContent?, ContentEditingOperationStatus>> MoveToRecycleBinAsync(Guid id, Guid userKey)
|
|
|
|
|
|
{
|
2023-04-14 09:44:52 +02:00
|
|
|
|
var currentUserId = await GetUserIdAsync(userKey);
|
2023-03-21 12:41:20 +01:00
|
|
|
|
return await HandleDeletionAsync(id, content => ContentService.MoveToRecycleBin(content, currentUserId), false);
|
|
|
|
|
|
}
|
2023-02-20 11:08:22 +01:00
|
|
|
|
|
2023-03-21 12:41:20 +01:00
|
|
|
|
public async Task<Attempt<IContent?, ContentEditingOperationStatus>> DeleteAsync(Guid id, Guid userKey)
|
|
|
|
|
|
{
|
2023-04-14 09:44:52 +02:00
|
|
|
|
var currentUserId = await GetUserIdAsync(userKey);
|
2023-03-21 12:41:20 +01:00
|
|
|
|
return await HandleDeletionAsync(id, content => ContentService.Delete(content, currentUserId), false);
|
|
|
|
|
|
}
|
2023-02-20 11:08:22 +01:00
|
|
|
|
|
2023-04-14 09:44:52 +02:00
|
|
|
|
public async Task<Attempt<IContent?, ContentEditingOperationStatus>> MoveAsync(Guid id, Guid? parentId, Guid userKey)
|
|
|
|
|
|
{
|
|
|
|
|
|
var currentUserId = await GetUserIdAsync(userKey);
|
|
|
|
|
|
return await HandleMoveAsync(id, parentId, (content, newParentId) => ContentService.Move(content, newParentId, currentUserId));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<Attempt<IContent?, ContentEditingOperationStatus>> CopyAsync(Guid id, Guid? parentId, bool relateToOriginal, bool includeDescendants, Guid userKey)
|
|
|
|
|
|
{
|
|
|
|
|
|
var currentUserId = await GetUserIdAsync(userKey);
|
|
|
|
|
|
return await HandleCopyAsync(id, parentId, (content, newParentId) => ContentService.Copy(content, newParentId, relateToOriginal, includeDescendants, currentUserId));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-20 11:08:22 +01:00
|
|
|
|
protected override IContent Create(string? name, int parentId, IContentType contentType) => new Content(name, parentId, contentType);
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<ContentEditingOperationStatus> UpdateTemplateAsync(IContent content, Guid? templateKey)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (templateKey == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
content.TemplateId = null;
|
|
|
|
|
|
return ContentEditingOperationStatus.Success;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ITemplate? template = await _templateService.GetAsync(templateKey.Value);
|
|
|
|
|
|
if (template == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ContentEditingOperationStatus.TemplateNotFound;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IContentType contentType = ContentTypeService.Get(content.ContentTypeId)
|
|
|
|
|
|
?? throw new ArgumentException("The content type was not found", nameof(content));
|
|
|
|
|
|
if (contentType.IsAllowedTemplate(template.Alias) == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ContentEditingOperationStatus.TemplateNotAllowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
content.TemplateId = template.Id;
|
|
|
|
|
|
return ContentEditingOperationStatus.Success;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-14 09:44:52 +02:00
|
|
|
|
private async Task<ContentEditingOperationStatus> Save(IContent content, Guid userKey)
|
2023-02-20 11:08:22 +01:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2023-04-14 09:44:52 +02:00
|
|
|
|
var currentUserId = await GetUserIdAsync(userKey);
|
2023-03-21 12:41:20 +01:00
|
|
|
|
OperationResult saveResult = ContentService.Save(content, currentUserId);
|
2023-02-20 11:08:22 +01:00
|
|
|
|
return saveResult.Result switch
|
|
|
|
|
|
{
|
|
|
|
|
|
// these are the only result states currently expected from Save
|
|
|
|
|
|
OperationResultType.Success => ContentEditingOperationStatus.Success,
|
|
|
|
|
|
OperationResultType.FailedCancelledByEvent => ContentEditingOperationStatus.CancelledByNotification,
|
|
|
|
|
|
|
|
|
|
|
|
// for any other state we'll return "unknown" so we know that we need to amend this
|
|
|
|
|
|
_ => ContentEditingOperationStatus.Unknown
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, "Content save operation failed");
|
|
|
|
|
|
return ContentEditingOperationStatus.Unknown;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-04-14 09:44:52 +02:00
|
|
|
|
|
2023-04-14 12:03:25 +02:00
|
|
|
|
private async Task<int> GetUserIdAsync(Guid userKey) => await _userIdKeyResolver.GetAsync(userKey);
|
2023-02-20 11:08:22 +01:00
|
|
|
|
}
|