Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
058798c420 | ||
|
|
a56652880d | ||
|
|
8bc3b8b032 | ||
|
|
922fbc8c09 | ||
|
|
f0031a741e | ||
|
|
6359d45130 | ||
|
|
908b4de8b5 | ||
|
|
8defac5b23 | ||
|
|
5fd8f54f87 | ||
|
|
99620721c3 | ||
|
|
0e9c45f334 | ||
|
|
1644b0e83e | ||
|
|
064c7bdb67 | ||
|
|
26ed5fb5bd | ||
|
|
e7ae4ab48c | ||
|
|
264f4c1574 | ||
|
|
0a2a518e5c | ||
|
|
28abcf334b | ||
|
|
2ce2b51d9c | ||
|
|
d7b4fadb64 | ||
|
|
3a53a9b6d0 | ||
|
|
feb07a20c1 | ||
|
|
ca9f0bd598 | ||
|
|
632b89d650 | ||
|
|
d6b5d57e03 | ||
|
|
523d96a0e1 | ||
|
|
eadd6ecfad | ||
|
|
fd59a8b1cf | ||
|
|
251b8a4d94 | ||
|
|
70b5c52ce3 | ||
|
|
d2e0416ab6 | ||
|
|
eea3c57864 | ||
|
|
b25ee766ed | ||
|
|
148ac5167f |
@@ -30,6 +30,7 @@ public abstract class DocumentControllerBase : ContentControllerBase
|
||||
where TContentModelBase : ContentModelBase<DocumentValueModel, DocumentVariantRequestModel>
|
||||
=> ContentEditingOperationStatusResult<TContentModelBase, DocumentValueModel, DocumentVariantRequestModel>(status, requestModel, validationResult);
|
||||
|
||||
// TODO ELEMENTS: move this to ContentControllerBase
|
||||
protected IActionResult DocumentPublishingOperationStatusResult(
|
||||
ContentPublishingOperationStatus status,
|
||||
IEnumerable<string>? invalidPropertyAliases = null,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ByKeyElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementService _elementService;
|
||||
private readonly IElementPresentationFactory _elementPresentationFactory;
|
||||
|
||||
public ByKeyElementController(IElementService elementService, IElementPresentationFactory elementPresentationFactory)
|
||||
{
|
||||
_elementService = elementService;
|
||||
_elementPresentationFactory = elementPresentationFactory;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(ElementResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id)
|
||||
{
|
||||
IElement? element = _elementService.GetById(id);
|
||||
if (element is null)
|
||||
{
|
||||
return Task.FromResult(ContentEditingOperationStatusResult(ContentEditingOperationStatus.NotFound));
|
||||
}
|
||||
|
||||
ContentScheduleCollection contentScheduleCollection = _elementService.GetContentScheduleByContentId(id);
|
||||
|
||||
ElementResponseModel model = _elementPresentationFactory.CreateResponseModel(element, contentScheduleCollection);
|
||||
return Task.FromResult<IActionResult>(Ok(model));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class CopyElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementEditingService _elementEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public CopyElementController(
|
||||
IElementEditingService elementEditingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_elementEditingService = elementEditingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/copy")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Copy(CancellationToken cancellationToken, Guid id, CopyElementRequestModel copyElementRequestModel)
|
||||
{
|
||||
Attempt<IElement?, ContentEditingOperationStatus> result = await _elementEditingService.CopyAsync(
|
||||
id,
|
||||
copyElementRequestModel.Target?.Id,
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? CreatedAtId<ByKeyElementController>(controller => nameof(controller.ByKey), result.Result!.Key)
|
||||
: ContentEditingOperationStatusResult(result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class CreateElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementEditingPresentationFactory _elementEditingPresentationFactory;
|
||||
private readonly IElementEditingService _elementEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public CreateElementController(
|
||||
IElementEditingPresentationFactory elementEditingPresentationFactory,
|
||||
IElementEditingService elementEditingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_elementEditingPresentationFactory = elementEditingPresentationFactory;
|
||||
_elementEditingService = elementEditingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Create(CancellationToken cancellationToken, CreateElementRequestModel requestModel)
|
||||
{
|
||||
ElementCreateModel model = _elementEditingPresentationFactory.MapCreateModel(requestModel);
|
||||
Attempt<ElementCreateResult, ContentEditingOperationStatus> result =
|
||||
await _elementEditingService.CreateAsync(model, CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? CreatedAtId<ByKeyElementController>(controller => nameof(controller.ByKey), result.Result.Content!.Key)
|
||||
: ContentEditingOperationStatusResult(result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class DeleteElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementEditingService _elementEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public DeleteElementController(
|
||||
IElementEditingService elementEditingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_elementEditingService = elementEditingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id)
|
||||
{
|
||||
Attempt<IElement?, ContentEditingOperationStatus> result = await _elementEditingService.DeleteAsync(id, CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
: ContentEditingOperationStatusResult(result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Controllers.Content;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[VersionedApiBackOfficeRoute(Constants.UdiEntityType.Element)]
|
||||
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
|
||||
// TODO ELEMENTS: backoffice authorization policies
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
|
||||
public class ElementControllerBase : ContentControllerBase
|
||||
{
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Folder;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ByKeyElementFolderController : ElementFolderControllerBase
|
||||
{
|
||||
public ByKeyElementFolderController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IElementContainerService elementContainerService)
|
||||
: base(backOfficeSecurityAccessor, elementContainerService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(FolderResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id) => await GetFolderAsync(id);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Folder;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class CreateElementFolderController : ElementFolderControllerBase
|
||||
{
|
||||
public CreateElementFolderController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IElementContainerService elementContainerService)
|
||||
: base(backOfficeSecurityAccessor, elementContainerService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Create(CancellationToken cancellationToken, CreateFolderRequestModel createFolderRequestModel)
|
||||
=> await CreateFolderAsync<ByKeyElementFolderController>(
|
||||
createFolderRequestModel,
|
||||
controller => nameof(controller.ByKey));
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class DeleteElementFolderController : ElementFolderControllerBase
|
||||
{
|
||||
public DeleteElementFolderController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IElementContainerService elementContainerService)
|
||||
: base(backOfficeSecurityAccessor, elementContainerService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id) => await DeleteFolderAsync(id);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
|
||||
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Element}/folder")]
|
||||
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
|
||||
// TODO ELEMENTS: backoffice authorization policies
|
||||
public abstract class ElementFolderControllerBase : FolderManagementControllerBase<IElement>
|
||||
{
|
||||
protected ElementFolderControllerBase(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IElementContainerService elementContainerService)
|
||||
: base(backOfficeSecurityAccessor, elementContainerService)
|
||||
{
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Folder;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class UpdateElementFolderController : ElementFolderControllerBase
|
||||
{
|
||||
public UpdateElementFolderController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IElementContainerService elementContainerService)
|
||||
: base(backOfficeSecurityAccessor, elementContainerService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Update(CancellationToken cancellationToken, Guid id, UpdateFolderResponseModel updateFolderResponseModel)
|
||||
=> await UpdateFolderAsync(id, updateFolderResponseModel);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Item;
|
||||
|
||||
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Element}")]
|
||||
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
|
||||
public class ElementItemControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element.Item;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Item;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ItemElementItemController : ElementItemControllerBase
|
||||
{
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly IElementPresentationFactory _elementPresentationFactory;
|
||||
|
||||
public ItemElementItemController(
|
||||
IEntityService entityService,
|
||||
IElementPresentationFactory elementPresentationFactory)
|
||||
{
|
||||
_entityService = entityService;
|
||||
_elementPresentationFactory = elementPresentationFactory;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<ElementItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Item(
|
||||
CancellationToken cancellationToken,
|
||||
[FromQuery(Name = "id")] HashSet<Guid> ids)
|
||||
{
|
||||
if (ids.Count is 0)
|
||||
{
|
||||
return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<ElementItemResponseModel>()));
|
||||
}
|
||||
|
||||
IEnumerable<IElementEntitySlim> elements = _entityService
|
||||
.GetAll(UmbracoObjectTypes.Element, ids.ToArray())
|
||||
.OfType<IElementEntitySlim>();
|
||||
|
||||
IEnumerable<ElementItemResponseModel> responseModels = elements.Select(_elementPresentationFactory.CreateItemResponseModel);
|
||||
return Task.FromResult<IActionResult>(Ok(responseModels));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class MoveElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementEditingService _elementEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public MoveElementController(
|
||||
IElementEditingService elementEditingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_elementEditingService = elementEditingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/move")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Move(CancellationToken cancellationToken, Guid id, MoveElementRequestModel moveElementRequestModel)
|
||||
{
|
||||
Attempt<ContentEditingOperationStatus> result = await _elementEditingService.MoveAsync(
|
||||
id,
|
||||
moveElementRequestModel.Target?.Id,
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
: ContentEditingOperationStatusResult(result.Result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.ContentPublishing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class PublishElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementPublishingService _elementPublishingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IDocumentPresentationFactory _documentPresentationFactory;
|
||||
|
||||
public PublishElementController(
|
||||
IElementPublishingService elementPublishingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory)
|
||||
{
|
||||
_elementPublishingService = elementPublishingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_documentPresentationFactory = documentPresentationFactory;
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/publish")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Publish(CancellationToken cancellationToken, Guid id, PublishElementRequestModel requestModel)
|
||||
{
|
||||
// TODO ELEMENTS: IDocumentPresentationFactory carries the implementation of this mapping - it should probably be renamed
|
||||
var tempModel = new PublishDocumentRequestModel { PublishSchedules = requestModel.PublishSchedules };
|
||||
Attempt<List<CulturePublishScheduleModel>, ContentPublishingOperationStatus> modelResult = _documentPresentationFactory.CreateCulturePublishScheduleModels(tempModel);
|
||||
|
||||
if (modelResult.Success is false)
|
||||
{
|
||||
// TODO ELEMENTS: use refactored DocumentPublishingOperationStatusResult from DocumentControllerBase once it's ready
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
Attempt<ContentPublishingResult, ContentPublishingOperationStatus> attempt = await _elementPublishingService.PublishAsync(
|
||||
id,
|
||||
modelResult.Result,
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
return attempt.Success
|
||||
? Ok()
|
||||
// TODO ELEMENTS: use refactored DocumentPublishingOperationStatusResult from DocumentControllerBase once it's ready
|
||||
: BadRequest();
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsElementTreeController : ElementTreeControllerBase
|
||||
{
|
||||
public AncestorsElementTreeController(IEntityService entityService, IUmbracoMapper umbracoMapper, IElementPresentationFactory elementPresentationFactory)
|
||||
: base(entityService, umbracoMapper, elementPresentationFactory)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<ElementTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<ElementTreeItemResponseModel>>> Ancestors(CancellationToken cancellationToken, Guid descendantId)
|
||||
=> await GetAncestors(descendantId);
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ChildrenElementTreeController : ElementTreeControllerBase
|
||||
{
|
||||
public ChildrenElementTreeController(IEntityService entityService, IUmbracoMapper umbracoMapper, IElementPresentationFactory elementPresentationFactory)
|
||||
: base(entityService, umbracoMapper, elementPresentationFactory)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<ElementTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<ElementTreeItemResponseModel>>> Children(CancellationToken cancellationToken, Guid parentId, int skip = 0, int take = 100, bool foldersOnly = false)
|
||||
{
|
||||
RenderFoldersOnly(foldersOnly);
|
||||
return await GetChildren(parentId, skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Controllers.Tree;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
|
||||
|
||||
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Tree}/{Constants.UdiEntityType.Element}")]
|
||||
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
|
||||
// TODO ELEMENTS: backoffice authorization policies
|
||||
public class ElementTreeControllerBase : FolderTreeControllerBase<ElementTreeItemResponseModel>
|
||||
{
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
private readonly IElementPresentationFactory _elementPresentationFactory;
|
||||
|
||||
public ElementTreeControllerBase(IEntityService entityService, IUmbracoMapper umbracoMapper, IElementPresentationFactory elementPresentationFactory)
|
||||
: base(entityService)
|
||||
{
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_elementPresentationFactory = elementPresentationFactory;
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Element;
|
||||
|
||||
protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.ElementContainer;
|
||||
|
||||
protected override Ordering ItemOrdering
|
||||
{
|
||||
get
|
||||
{
|
||||
var ordering = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.NodeObjectType), Direction.Descending); // We need to override to change direction
|
||||
ordering.Next = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text));
|
||||
|
||||
return ordering;
|
||||
}
|
||||
}
|
||||
|
||||
protected override ElementTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
=> entities.Select(entity =>
|
||||
{
|
||||
ElementTreeItemResponseModel responseModel = MapTreeItemViewModel(parentKey, entity);
|
||||
if (entity is IElementEntitySlim elementEntitySlim)
|
||||
{
|
||||
responseModel.HasChildren = false;
|
||||
responseModel.DocumentType = _umbracoMapper.Map<DocumentTypeReferenceResponseModel>(elementEntitySlim)!;
|
||||
responseModel.Variants = _elementPresentationFactory.CreateVariantsItemResponseModels(elementEntitySlim);
|
||||
}
|
||||
|
||||
return responseModel;
|
||||
}).ToArray();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class RootElementTreeController : ElementTreeControllerBase
|
||||
{
|
||||
public RootElementTreeController(IEntityService entityService, IUmbracoMapper umbracoMapper, IElementPresentationFactory elementPresentationFactory)
|
||||
: base(entityService, umbracoMapper, elementPresentationFactory)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<ElementTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<ElementTreeItemResponseModel>>> Root(
|
||||
CancellationToken cancellationToken,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
bool foldersOnly = false)
|
||||
{
|
||||
RenderFoldersOnly(foldersOnly);
|
||||
return await GetRoot(skip, take);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
|
||||
|
||||
public class SiblingsElementTreeController : ElementTreeControllerBase
|
||||
{
|
||||
public SiblingsElementTreeController(IEntityService entityService, IUmbracoMapper umbracoMapper, IElementPresentationFactory elementPresentationFactory)
|
||||
: base(entityService, umbracoMapper, elementPresentationFactory)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("siblings")]
|
||||
[ProducesResponseType(typeof(SubsetViewModel<ElementTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<SubsetViewModel<ElementTreeItemResponseModel>>> Siblings(
|
||||
CancellationToken cancellationToken,
|
||||
Guid target,
|
||||
int before,
|
||||
int after,
|
||||
bool foldersOnly = false)
|
||||
{
|
||||
RenderFoldersOnly(foldersOnly);
|
||||
return await GetSiblings(target, before, after);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class UnpublishElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementPublishingService _elementPublishingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IDocumentPresentationFactory _documentPresentationFactory;
|
||||
|
||||
public UnpublishElementController(
|
||||
IElementPublishingService elementPublishingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory)
|
||||
{
|
||||
_elementPublishingService = elementPublishingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_documentPresentationFactory = documentPresentationFactory;
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/unpublish")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Unpublish(CancellationToken cancellationToken, Guid id, UnpublishElementRequestModel requestModel)
|
||||
{
|
||||
Attempt<ContentPublishingOperationStatus> attempt = await _elementPublishingService.UnpublishAsync(
|
||||
id,
|
||||
requestModel.Cultures,
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
return attempt.Success
|
||||
? Ok()
|
||||
// TODO ELEMENTS: use refactored DocumentPublishingOperationStatusResult from DocumentControllerBase once it's ready
|
||||
: BadRequest();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class UpdateElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementEditingPresentationFactory _elementEditingPresentationFactory;
|
||||
private readonly IElementEditingService _elementEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public UpdateElementController(
|
||||
IElementEditingPresentationFactory elementEditingPresentationFactory,
|
||||
IElementEditingService elementEditingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_elementEditingPresentationFactory = elementEditingPresentationFactory;
|
||||
_elementEditingService = elementEditingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Update(CancellationToken cancellationToken, Guid id, UpdateElementRequestModel requestModel)
|
||||
{
|
||||
ElementUpdateModel model = _elementEditingPresentationFactory.MapUpdateModel(requestModel);
|
||||
Attempt<ElementUpdateResult, ContentEditingOperationStatus> result =
|
||||
await _elementEditingService.UpdateAsync(id, model, CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
: ContentEditingOperationStatusResult(result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ValidateCreateElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementEditingPresentationFactory _elementEditingPresentationFactory;
|
||||
private readonly IElementEditingService _elementEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public ValidateCreateElementController(
|
||||
IElementEditingPresentationFactory elementEditingPresentationFactory,
|
||||
IElementEditingService elementEditingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_elementEditingPresentationFactory = elementEditingPresentationFactory;
|
||||
_elementEditingService = elementEditingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPost("validate")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Validate(CancellationToken cancellationToken, CreateElementRequestModel requestModel)
|
||||
{
|
||||
ElementCreateModel model = _elementEditingPresentationFactory.MapCreateModel(requestModel);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
|
||||
await _elementEditingService.ValidateCreateAsync(model, CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
: ContentEditingOperationStatusResult(result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Element;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ValidateUpdateElementController : ElementControllerBase
|
||||
{
|
||||
private readonly IElementEditingPresentationFactory _elementEditingPresentationFactory;
|
||||
private readonly IElementEditingService _elementEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public ValidateUpdateElementController(
|
||||
IElementEditingPresentationFactory elementEditingPresentationFactory,
|
||||
IElementEditingService elementEditingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_elementEditingPresentationFactory = elementEditingPresentationFactory;
|
||||
_elementEditingService = elementEditingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/validate")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Validate(CancellationToken cancellationToken, Guid id, ValidateUpdateElementRequestModel requestModel)
|
||||
{
|
||||
ValidateElementUpdateModel model = _elementEditingPresentationFactory.MapValidateUpdateModel(requestModel);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
|
||||
await _elementEditingService.ValidateUpdateAsync(id, model, CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
: ContentEditingOperationStatusResult(result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Mapping.Element;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
|
||||
internal static class ElementBuilderExtensions
|
||||
{
|
||||
internal static IUmbracoBuilder AddElements(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddTransient<IElementPresentationFactory, ElementPresentationFactory>();
|
||||
builder.Services.AddTransient<IElementEditingPresentationFactory, ElementEditingPresentationFactory>();
|
||||
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
|
||||
.Add<ElementMapDefinition>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public static partial class UmbracoBuilderExtensions
|
||||
.AddConfigurationFactories()
|
||||
.AddDocuments()
|
||||
.AddDocumentTypes()
|
||||
.AddElements()
|
||||
.AddMedia()
|
||||
.AddMediaTypes()
|
||||
.AddMemberGroups()
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
internal sealed class ElementEditingPresentationFactory : ContentEditingPresentationFactory<ElementValueModel, ElementVariantRequestModel>, IElementEditingPresentationFactory
|
||||
{
|
||||
public ElementCreateModel MapCreateModel(CreateElementRequestModel requestModel)
|
||||
{
|
||||
ElementCreateModel model = MapContentEditingModel<ElementCreateModel>(requestModel);
|
||||
model.Key = requestModel.Id;
|
||||
model.ContentTypeKey = requestModel.DocumentType.Id;
|
||||
model.ParentKey = requestModel.Parent?.Id;
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
public ElementUpdateModel MapUpdateModel(UpdateElementRequestModel requestModel)
|
||||
=> MapContentEditingModel<ElementUpdateModel>(requestModel);
|
||||
|
||||
public ValidateElementUpdateModel MapValidateUpdateModel(ValidateUpdateElementRequestModel requestModel)
|
||||
{
|
||||
ValidateElementUpdateModel model = MapContentEditingModel<ValidateElementUpdateModel>(requestModel);
|
||||
model.Cultures = requestModel.Cultures;
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Umbraco.Cms.Api.Management.Mapping.Content;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element.Item;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
// TODO ELEMENTS: lots of code here was duplicated from DocumentPresentationFactory - abstract and refactor
|
||||
public class ElementPresentationFactory : IElementPresentationFactory
|
||||
{
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
|
||||
public ElementPresentationFactory(IUmbracoMapper umbracoMapper, IIdKeyMap idKeyMap)
|
||||
{
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_idKeyMap = idKeyMap;
|
||||
}
|
||||
|
||||
public ElementResponseModel CreateResponseModel(IElement element, ContentScheduleCollection schedule)
|
||||
{
|
||||
ElementResponseModel responseModel = _umbracoMapper.Map<ElementResponseModel>(element)!;
|
||||
_umbracoMapper.Map(schedule, responseModel);
|
||||
|
||||
return responseModel;
|
||||
}
|
||||
|
||||
public ElementItemResponseModel CreateItemResponseModel(IElementEntitySlim entity)
|
||||
{
|
||||
Attempt<Guid> parentKeyAttempt = _idKeyMap.GetKeyForId(entity.ParentId, UmbracoObjectTypes.ElementContainer);
|
||||
|
||||
var responseModel = new ElementItemResponseModel
|
||||
{
|
||||
Id = entity.Key,
|
||||
Parent = parentKeyAttempt.Success ? new ReferenceByIdModel { Id = parentKeyAttempt.Result } : null,
|
||||
HasChildren = entity.HasChildren,
|
||||
};
|
||||
|
||||
responseModel.DocumentType = _umbracoMapper.Map<DocumentTypeReferenceResponseModel>(entity)!;
|
||||
|
||||
responseModel.Variants = CreateVariantsItemResponseModels(entity);
|
||||
|
||||
return responseModel;
|
||||
}
|
||||
|
||||
public IEnumerable<ElementVariantItemResponseModel> CreateVariantsItemResponseModels(IElementEntitySlim entity)
|
||||
{
|
||||
if (entity.Variations.VariesByCulture() is false)
|
||||
{
|
||||
var model = new ElementVariantItemResponseModel()
|
||||
{
|
||||
Name = entity.Name ?? string.Empty,
|
||||
State = DocumentVariantStateHelper.GetState(entity, null),
|
||||
Culture = null,
|
||||
};
|
||||
|
||||
yield return model;
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> cultureNamePair in entity.CultureNames)
|
||||
{
|
||||
var model = new ElementVariantItemResponseModel()
|
||||
{
|
||||
Name = cultureNamePair.Value,
|
||||
Culture = cultureNamePair.Key,
|
||||
State = DocumentVariantStateHelper.GetState(entity, cultureNamePair.Key)
|
||||
};
|
||||
|
||||
yield return model;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public interface IElementEditingPresentationFactory
|
||||
{
|
||||
ElementCreateModel MapCreateModel(CreateElementRequestModel requestModel);
|
||||
|
||||
ElementUpdateModel MapUpdateModel(UpdateElementRequestModel requestModel);
|
||||
|
||||
ValidateElementUpdateModel MapValidateUpdateModel(ValidateUpdateElementRequestModel requestModel);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element.Item;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public interface IElementPresentationFactory
|
||||
{
|
||||
ElementResponseModel CreateResponseModel(IElement element, ContentScheduleCollection schedule);
|
||||
|
||||
ElementItemResponseModel CreateItemResponseModel(IElementEntitySlim entity);
|
||||
|
||||
IEnumerable<ElementVariantItemResponseModel> CreateVariantsItemResponseModels(IElementEntitySlim entity);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
@@ -108,4 +109,33 @@ public abstract class ContentMapDefinition<TContent, TValueViewModel, TVariantVi
|
||||
}))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
protected void MapContentScheduleCollection<TContentResponseModel, TPublishableVariantResponseModelBase>(ContentScheduleCollection source, TContentResponseModel target, MapperContext context)
|
||||
where TContentResponseModel : ContentResponseModelBase<TValueViewModel, TPublishableVariantResponseModelBase>
|
||||
where TPublishableVariantResponseModelBase : PublishableVariantResponseModelBase, TVariantViewModel
|
||||
{
|
||||
foreach (ContentSchedule schedule in source.FullSchedule)
|
||||
{
|
||||
TPublishableVariantResponseModelBase? variant = target.Variants
|
||||
.FirstOrDefault(v =>
|
||||
v.Culture == schedule.Culture ||
|
||||
(IsInvariant(v.Culture) && IsInvariant(schedule.Culture)));
|
||||
if (variant is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (schedule.Action)
|
||||
{
|
||||
case ContentScheduleAction.Release:
|
||||
variant.ScheduledPublishDate = new DateTimeOffset(schedule.Date, TimeSpan.Zero);
|
||||
break;
|
||||
case ContentScheduleAction.Expire:
|
||||
variant.ScheduledUnpublishDate = new DateTimeOffset(schedule.Date, TimeSpan.Zero);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsInvariant(string? culture) => culture.IsNullOrWhiteSpace() || culture == Core.Constants.System.InvariantCulture;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ using Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Mapping.Content;
|
||||
|
||||
// TODO ELEMENTS: rename this to VariantStateHelper or ContentVariantStateHelper (depending on the new name for DocumentVariantState)
|
||||
internal static class DocumentVariantStateHelper
|
||||
{
|
||||
internal static DocumentVariantState GetState(IContent content, string? culture)
|
||||
internal static DocumentVariantState GetState(IPublishableContentBase content, string? culture)
|
||||
=> GetState(
|
||||
content,
|
||||
culture,
|
||||
@@ -26,6 +27,16 @@ internal static class DocumentVariantStateHelper
|
||||
content.EditedCultures,
|
||||
content.PublishedCultures);
|
||||
|
||||
internal static DocumentVariantState GetState(IElementEntitySlim element, string? culture)
|
||||
=> GetState(
|
||||
element,
|
||||
culture,
|
||||
element.Edited,
|
||||
element.Published,
|
||||
element.CultureNames.Keys,
|
||||
element.EditedCultures,
|
||||
element.PublishedCultures);
|
||||
|
||||
private static DocumentVariantState GetState(IEntity entity, string? culture, bool edited, bool published, IEnumerable<string> availableCultures, IEnumerable<string> editedCultures, IEnumerable<string> publishedCultures)
|
||||
{
|
||||
if (entity.Id <= 0 || (culture is not null && availableCultures.Contains(culture) is false))
|
||||
|
||||
@@ -133,29 +133,5 @@ public class DocumentMapDefinition : ContentMapDefinition<IContent, DocumentValu
|
||||
}
|
||||
|
||||
private void Map(ContentScheduleCollection source, DocumentResponseModel target, MapperContext context)
|
||||
{
|
||||
foreach (ContentSchedule schedule in source.FullSchedule)
|
||||
{
|
||||
DocumentVariantResponseModel? variant = target.Variants
|
||||
.FirstOrDefault(v =>
|
||||
v.Culture == schedule.Culture ||
|
||||
(IsInvariant(v.Culture) && IsInvariant(schedule.Culture)));
|
||||
if (variant is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (schedule.Action)
|
||||
{
|
||||
case ContentScheduleAction.Release:
|
||||
variant.ScheduledPublishDate = new DateTimeOffset(schedule.Date, TimeSpan.Zero);
|
||||
break;
|
||||
case ContentScheduleAction.Expire:
|
||||
variant.ScheduledUnpublishDate = new DateTimeOffset(schedule.Date, TimeSpan.Zero);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsInvariant(string? culture) => culture.IsNullOrWhiteSpace() || culture == Core.Constants.System.InvariantCulture;
|
||||
=> MapContentScheduleCollection<DocumentResponseModel, DocumentVariantResponseModel>(source, target, context);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ public class DocumentTypeMapDefinition : ContentTypeMapDefinition<IContentType,
|
||||
mapper.Define<ISimpleContentType, DocumentTypeCollectionReferenceResponseModel>((_, _) => new DocumentTypeCollectionReferenceResponseModel(), Map);
|
||||
mapper.Define<IContentEntitySlim, DocumentTypeReferenceResponseModel>((_, _) => new DocumentTypeReferenceResponseModel(), Map);
|
||||
mapper.Define<IDocumentEntitySlim, DocumentTypeReferenceResponseModel>((_, _) => new DocumentTypeReferenceResponseModel(), Map);
|
||||
mapper.Define<IElementEntitySlim, DocumentTypeReferenceResponseModel>((_, _) => new DocumentTypeReferenceResponseModel(), Map);
|
||||
mapper.Define<IContent, DocumentTypeBlueprintItemResponseModel>((_, _) => new DocumentTypeBlueprintItemResponseModel(), Map);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using Umbraco.Cms.Api.Management.Mapping.Content;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Mapping.Element;
|
||||
|
||||
public class ElementMapDefinition : ContentMapDefinition<IElement, ElementValueResponseModel, ElementVariantResponseModel>, IMapDefinition
|
||||
{
|
||||
public ElementMapDefinition(PropertyEditorCollection propertyEditorCollection)
|
||||
: base(propertyEditorCollection)
|
||||
{
|
||||
}
|
||||
|
||||
public void DefineMaps(IUmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IElement, ElementResponseModel>((_, _) => new ElementResponseModel(), Map);
|
||||
mapper.Define<ContentScheduleCollection, ElementResponseModel>(Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(IElement source, ElementResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Key;
|
||||
target.DocumentType = context.Map<DocumentTypeReferenceResponseModel>(source.ContentType)!;
|
||||
target.Values = MapValueViewModels(source.Properties);
|
||||
target.Variants = MapVariantViewModels(
|
||||
source,
|
||||
(culture, _, documentVariantViewModel) =>
|
||||
{
|
||||
documentVariantViewModel.State = DocumentVariantStateHelper.GetState(source, culture);
|
||||
documentVariantViewModel.PublishDate = culture == null
|
||||
? source.PublishDate
|
||||
: source.GetPublishDate(culture);
|
||||
});
|
||||
target.IsTrashed = source.Trashed;
|
||||
}
|
||||
|
||||
private void Map(ContentScheduleCollection source, ElementResponseModel target, MapperContext context)
|
||||
=> MapContentScheduleCollection<ElementResponseModel, ElementVariantResponseModel>(source, target, context);
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
|
||||
public abstract class PublishableVariantResponseModelBase : VariantResponseModelBase
|
||||
{
|
||||
public DocumentVariantState State { get; set; }
|
||||
|
||||
public DateTimeOffset? PublishDate { get; set; }
|
||||
|
||||
public DateTimeOffset? ScheduledPublishDate { get; set; }
|
||||
|
||||
public DateTimeOffset? ScheduledUnpublishDate { get; set; }
|
||||
}
|
||||
@@ -2,16 +2,8 @@ using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
public class DocumentVariantResponseModel : VariantResponseModelBase, IHasFlags
|
||||
public class DocumentVariantResponseModel : PublishableVariantResponseModelBase, IHasFlags
|
||||
{
|
||||
public DocumentVariantState State { get; set; }
|
||||
|
||||
public DateTimeOffset? PublishDate { get; set; }
|
||||
|
||||
public DateTimeOffset? ScheduledPublishDate { get; set; }
|
||||
|
||||
public DateTimeOffset? ScheduledUnpublishDate { get; set; }
|
||||
|
||||
private readonly List<FlagModel> _flags = [];
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/// <summary>
|
||||
/// The saved state of a content item
|
||||
/// </summary>
|
||||
// TODO ELEMENTS: move this to ViewModels.Content and rename it to VariantState or ContentVariantState (shared between document and element variants)
|
||||
public enum DocumentVariantState
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -5,7 +5,7 @@ public class PublishDocumentRequestModel
|
||||
public required IEnumerable<CultureAndScheduleRequestModel> PublishSchedules { get; set; }
|
||||
}
|
||||
|
||||
|
||||
// TODO ELEMENTS: move the following classes to ViewModels.Content
|
||||
public class CultureAndScheduleRequestModel
|
||||
{
|
||||
/// <summary>
|
||||
@@ -19,7 +19,6 @@ public class CultureAndScheduleRequestModel
|
||||
public ScheduleRequestModel? Schedule { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class ScheduleRequestModel
|
||||
{
|
||||
public DateTimeOffset? PublishTime { get; set; }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class CopyElementRequestModel
|
||||
{
|
||||
public ReferenceByIdModel? Target { get; set; }
|
||||
|
||||
// TODO ELEMENTS: do we want a relate-to-original feature for elements?
|
||||
// public bool RelateToOriginal { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class CreateElementRequestModel : CreateContentWithParentRequestModelBase<ElementValueModel, ElementVariantRequestModel>
|
||||
{
|
||||
public required ReferenceByIdModel DocumentType { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class ElementResponseModel : ElementResponseModelBase<ElementValueResponseModel, ElementVariantResponseModel>
|
||||
{
|
||||
public bool IsTrashed { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public abstract class ElementResponseModelBase<TValueResponseModelBase, TVariantResponseModel>
|
||||
: ContentResponseModelBase<TValueResponseModelBase, TVariantResponseModel>
|
||||
where TValueResponseModelBase : ValueModelBase
|
||||
where TVariantResponseModel : VariantResponseModelBase
|
||||
{
|
||||
public DocumentTypeReferenceResponseModel DocumentType { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class ElementValueModel : ValueModelBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class ElementValueResponseModel : ValueResponseModelBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class ElementVariantItemResponseModel : VariantItemResponseModelBase
|
||||
{
|
||||
public required DocumentVariantState State { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class ElementVariantRequestModel : VariantModelBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class ElementVariantResponseModel : PublishableVariantResponseModelBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Item;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element.Item;
|
||||
|
||||
public class ElementItemResponseModel : ItemResponseModelBase
|
||||
{
|
||||
public ReferenceByIdModel? Parent { get; set; }
|
||||
|
||||
public bool HasChildren { get; set; }
|
||||
|
||||
public DocumentTypeReferenceResponseModel DocumentType { get; set; } = new();
|
||||
|
||||
public IEnumerable<ElementVariantItemResponseModel> Variants { get; set; } = Enumerable.Empty<ElementVariantItemResponseModel>();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class MoveElementRequestModel
|
||||
{
|
||||
public ReferenceByIdModel? Target { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class PublishElementRequestModel
|
||||
{
|
||||
public required IEnumerable<CultureAndScheduleRequestModel> PublishSchedules { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class UnpublishElementRequestModel
|
||||
{
|
||||
public ISet<string>? Cultures { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class UpdateElementRequestModel : UpdateContentRequestModelBase<ElementValueModel, ElementVariantRequestModel>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
public class ValidateUpdateElementRequestModel : UpdateElementRequestModel
|
||||
{
|
||||
public ISet<string>? Cultures { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Element;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
|
||||
public class ElementTreeItemResponseModel : FolderTreeItemResponseModel
|
||||
{
|
||||
public DocumentTypeReferenceResponseModel? DocumentType { get; set; }
|
||||
|
||||
public IEnumerable<ElementVariantItemResponseModel> Variants { get; set; } = [];
|
||||
}
|
||||
@@ -227,6 +227,18 @@ public static class DistributedCacheExtensions
|
||||
|
||||
#endregion
|
||||
|
||||
#region ElementCacheRefresher
|
||||
|
||||
public static void RefreshAllElementCache(this DistributedCache dc)
|
||||
// note: refresh all element cache does refresh content types too
|
||||
=> dc.RefreshByPayload(ElementCacheRefresher.UniqueId, new ElementCacheRefresher.JsonPayload(0, Guid.Empty, TreeChangeTypes.RefreshAll).Yield());
|
||||
|
||||
|
||||
public static void RefreshElementCache(this DistributedCache dc, IEnumerable<TreeChange<IElement>> changes)
|
||||
=> dc.RefreshByPayload(ElementCacheRefresher.UniqueId, changes.DistinctBy(x => (x.Item.Id, x.Item.Key, x.ChangeTypes)).Select(x => new ElementCacheRefresher.JsonPayload(x.Item.Id, x.Item.Key, x.ChangeTypes)));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Published Snapshot
|
||||
|
||||
public static void RefreshAllPublishedSnapshot(this DistributedCache dc)
|
||||
@@ -234,6 +246,7 @@ public static class DistributedCacheExtensions
|
||||
// note: refresh all content & media caches does refresh content types too
|
||||
dc.RefreshAllContentCache();
|
||||
dc.RefreshAllMediaCache();
|
||||
dc.RefreshAllElementCache();
|
||||
dc.RefreshAllDomainCache();
|
||||
}
|
||||
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Services.Changes;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Cache;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class ElementTreeChangeDistributedCacheNotificationHandler : TreeChangeDistributedCacheNotificationHandlerBase<IElement, ElementTreeChangeNotification>
|
||||
{
|
||||
private readonly DistributedCache _distributedCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ElementTreeChangeDistributedCacheNotificationHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="distributedCache">The distributed cache.</param>
|
||||
public ElementTreeChangeDistributedCacheNotificationHandler(DistributedCache distributedCache)
|
||||
=> _distributedCache = distributedCache;
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Scheduled for removal in Umbraco 18.")]
|
||||
protected override void Handle(IEnumerable<TreeChange<IElement>> entities)
|
||||
=> Handle(entities, new Dictionary<string, object?>());
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Handle(IEnumerable<TreeChange<IElement>> entities, IDictionary<string, object?> state)
|
||||
=> _distributedCache.RefreshElementCache(entities);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Changes;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Cache;
|
||||
|
||||
public sealed class ElementCacheRefresher : PayloadCacheRefresherBase<ElementCacheRefresherNotification, ElementCacheRefresher.JsonPayload>
|
||||
{
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
private readonly IElementCacheService _elementCacheService;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
|
||||
public ElementCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
IIdKeyMap idKeyMap,
|
||||
IEventAggregator eventAggregator,
|
||||
ICacheRefresherNotificationFactory factory,
|
||||
IElementCacheService elementCacheService,
|
||||
ICacheManager cacheManager)
|
||||
: base(appCaches, serializer, eventAggregator, factory)
|
||||
{
|
||||
_idKeyMap = idKeyMap;
|
||||
_elementCacheService = elementCacheService;
|
||||
|
||||
// TODO: Use IElementsCache instead of ICacheManager, see ContentCacheRefresher for more information.
|
||||
_cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
#region Json
|
||||
|
||||
public class JsonPayload
|
||||
{
|
||||
public JsonPayload(int id, Guid key, TreeChangeTypes changeTypes)
|
||||
{
|
||||
Id = id;
|
||||
Key = key;
|
||||
ChangeTypes = changeTypes;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
|
||||
public Guid Key { get; }
|
||||
|
||||
public TreeChangeTypes ChangeTypes { get; }
|
||||
|
||||
// TODO ELEMENTS: should we support (un)published cultures in this payload? see ContentCacheRefresher.JsonPayload
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Define
|
||||
|
||||
public static readonly Guid UniqueId = Guid.Parse("EE5BB23A-A656-4F7E-A234-16F21AAABFD1");
|
||||
|
||||
public override Guid RefresherUniqueId => UniqueId;
|
||||
|
||||
public override string Name => "Element Cache Refresher";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Refresher
|
||||
|
||||
public override void Refresh(JsonPayload[] payloads)
|
||||
{
|
||||
// TODO ELEMENTS: implement recycle bin
|
||||
// AppCaches.RuntimeCache.ClearByKey(CacheKeys.ElementRecycleBinCacheKey);
|
||||
|
||||
// Ideally, we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
|
||||
// The reason for this is that we have no way to know which elements are affected by the changes or what their keys are.
|
||||
// This is because currently published elements live exclusively in a JSON blob in the umbracoPropertyData table.
|
||||
// This means that the only way to resolve these keys is to actually parse this data with a specific value converter, and for all cultures, which is not possible.
|
||||
// If published elements become their own entities with relations, instead of just property data, we can revisit this.
|
||||
_cacheManager.ElementsCache.Clear();
|
||||
|
||||
IAppPolicyCache isolatedCache = AppCaches.IsolatedCaches.GetOrCreate<IElement>();
|
||||
|
||||
foreach (JsonPayload payload in payloads)
|
||||
{
|
||||
// By INT Id
|
||||
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IElement, int>(payload.Id));
|
||||
|
||||
// By GUID Key
|
||||
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IElement, Guid?>(payload.Key));
|
||||
|
||||
HandleMemoryCache(payload);
|
||||
|
||||
// TODO ELEMENTS: if we need published status caching for elements (e.g. for seeding purposes), make sure
|
||||
// it is kept in sync here (see ContentCacheRefresher)
|
||||
|
||||
if (payload.ChangeTypes == TreeChangeTypes.Remove)
|
||||
{
|
||||
_idKeyMap.ClearCache(payload.Id);
|
||||
}
|
||||
}
|
||||
|
||||
AppCaches.ClearPartialViewCache();
|
||||
|
||||
base.Refresh(payloads);
|
||||
}
|
||||
|
||||
private void HandleMemoryCache(JsonPayload payload)
|
||||
{
|
||||
if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
|
||||
{
|
||||
_elementCacheService.ClearMemoryCacheAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshNode) || payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch))
|
||||
{
|
||||
// NOTE: RefreshBranch might be triggered even though elements do not support branch publishing
|
||||
_elementCacheService.RefreshMemoryCacheAsync(payload.Key).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
|
||||
{
|
||||
_elementCacheService.RemoveFromMemoryCacheAsync(payload.Key).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
// these events should never trigger
|
||||
// everything should be JSON
|
||||
public override void RefreshAll() => throw new NotSupportedException();
|
||||
|
||||
public override void Refresh(int id) => throw new NotSupportedException();
|
||||
|
||||
public override void Refresh(Guid id) => throw new NotSupportedException();
|
||||
|
||||
public override void Remove(int id) => throw new NotSupportedException();
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -29,6 +29,10 @@ public static partial class Constants
|
||||
|
||||
public static readonly Guid DocumentType = new(Strings.DocumentType);
|
||||
|
||||
public static readonly Guid Element = new(Strings.Element);
|
||||
|
||||
public static readonly Guid ElementContainer = new(Strings.ElementContainer);
|
||||
|
||||
public static readonly Guid Media = new(Strings.Media);
|
||||
|
||||
public static readonly Guid MediaType = new(Strings.MediaType);
|
||||
@@ -91,6 +95,10 @@ public static partial class Constants
|
||||
|
||||
public const string DocumentType = "A2CB7800-F571-4787-9638-BC48539A0EFB";
|
||||
|
||||
public const string Element = "3D7B623C-94B1-487D-8554-A46EC37568BE";
|
||||
|
||||
public const string ElementContainer = "2815B0CF-9706-499F-AA2A-8A4C7AEF005D";
|
||||
|
||||
public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C";
|
||||
|
||||
public const string MediaRecycleBin = "CF3D8E34-1C1C-41e9-AE56-878B57B32113";
|
||||
|
||||
@@ -234,6 +234,11 @@ public static partial class Constants
|
||||
/// Configuration-less time.
|
||||
/// </summary>
|
||||
public const string PlainTime = "Umbraco.Plain.Time";
|
||||
|
||||
/// <summary>
|
||||
/// Element Picker.
|
||||
/// </summary>
|
||||
public const string ElementPicker = "Umbraco.ElementPicker";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -28,6 +28,7 @@ public static partial class Constants
|
||||
public const string DocumentType = "document-type";
|
||||
public const string DocumentTypeContainer = "document-type-container";
|
||||
public const string Element = "element";
|
||||
public const string ElementContainer = "element-container";
|
||||
public const string Media = "media";
|
||||
public const string MediaType = "media-type";
|
||||
public const string MediaTypeContainer = "media-type-container";
|
||||
|
||||
@@ -297,10 +297,15 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
Services.AddUnique<IContentPermissionService, ContentPermissionService>();
|
||||
Services.AddUnique<IDictionaryPermissionService, DictionaryPermissionService>();
|
||||
Services.AddUnique<IContentService, ContentService>();
|
||||
Services.AddUnique<IElementService, ElementService>();
|
||||
Services.AddUnique<IElementContainerService, ElementContainerService>();
|
||||
Services.AddUnique<IContentBlueprintEditingService, ContentBlueprintEditingService>();
|
||||
Services.AddUnique<IContentEditingService, ContentEditingService>();
|
||||
Services.AddUnique<IElementEditingService, ElementEditingService>();
|
||||
Services.AddUnique<IContentPublishingService, ContentPublishingService>();
|
||||
Services.AddUnique<IElementPublishingService, ElementPublishingService>();
|
||||
Services.AddUnique<IContentValidationService, ContentValidationService>();
|
||||
Services.AddUnique<IElementValidationService, ElementValidationService>();
|
||||
Services.AddUnique<IContentVersionCleanupPolicy, DefaultContentVersionCleanupPolicy>();
|
||||
Services.AddUnique<IMemberService, MemberService>();
|
||||
Services.AddUnique<IMemberValidationService, MemberValidationService>();
|
||||
|
||||
@@ -228,7 +228,7 @@ public static class ContentExtensions
|
||||
/// <summary>
|
||||
/// Gets the current status of the Content
|
||||
/// </summary>
|
||||
public static ContentStatus GetStatus(this IContent content, ContentScheduleCollection contentSchedule, string? culture = null)
|
||||
public static ContentStatus GetStatus(this IPublishableContentBase content, ContentScheduleCollection contentSchedule, string? culture = null)
|
||||
{
|
||||
if (content.Trashed)
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ public static class PublishedContentExtensions
|
||||
/// The specific culture to get the name for. If null is used the current culture is used (Default is
|
||||
/// null).
|
||||
/// </param>
|
||||
public static string Name(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, string? culture = null)
|
||||
public static string Name(this IPublishedElement content, IVariationContextAccessor? variationContextAccessor, string? culture = null)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
|
||||
@@ -76,6 +76,10 @@ public static class UdiGetterExtensions
|
||||
{
|
||||
entityType = Constants.UdiEntityType.DocumentBlueprintContainer;
|
||||
}
|
||||
else if (entity.ContainedObjectType == Constants.ObjectTypes.Element)
|
||||
{
|
||||
entityType = Constants.UdiEntityType.ElementContainer;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Contained object type {entity.ContainedObjectType} is not supported.");
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
@@ -9,12 +7,8 @@ namespace Umbraco.Cms.Core.Models;
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public class Content : ContentBase, IContent
|
||||
public class Content : PublishableContentBase, IContent
|
||||
{
|
||||
private HashSet<string>? _editedCultures;
|
||||
private bool _published;
|
||||
private PublishedState _publishedState;
|
||||
private ContentCultureInfosCollection? _publishInfos;
|
||||
private int? _templateId;
|
||||
|
||||
/// <summary>
|
||||
@@ -55,13 +49,6 @@ public class Content : ContentBase, IContent
|
||||
public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, string? culture = null)
|
||||
: base(name, parent, contentType, properties, culture)
|
||||
{
|
||||
if (contentType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contentType));
|
||||
}
|
||||
|
||||
_publishedState = PublishedState.Unpublished;
|
||||
PublishedVersionId = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -102,13 +89,6 @@ public class Content : ContentBase, IContent
|
||||
public Content(string? name, int parentId, IContentType? contentType, PropertyCollection properties, string? culture = null)
|
||||
: base(name, parentId, contentType, properties, culture)
|
||||
{
|
||||
if (contentType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contentType));
|
||||
}
|
||||
|
||||
_publishedState = PublishedState.Unpublished;
|
||||
PublishedVersionId = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,278 +106,13 @@ public class Content : ContentBase, IContent
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _templateId, nameof(TemplateId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this content item is published or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// the setter is should only be invoked from
|
||||
/// - the ContentFactory when creating a content entity from a dto
|
||||
/// - the ContentRepository when updating a content entity
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public bool Published
|
||||
{
|
||||
get => _published;
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(value, ref _published, nameof(Published));
|
||||
_publishedState = _published ? PublishedState.Published : PublishedState.Unpublished;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published state of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The state should be Published or Unpublished, depending on whether Published
|
||||
/// is true or false, but can also temporarily be Publishing or Unpublishing when the
|
||||
/// content item is about to be saved.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public PublishedState PublishedState
|
||||
{
|
||||
get => _publishedState;
|
||||
set
|
||||
{
|
||||
if (value != PublishedState.Publishing && value != PublishedState.Unpublishing)
|
||||
{
|
||||
throw new ArgumentException("Invalid state, only Publishing and Unpublishing are accepted.");
|
||||
}
|
||||
|
||||
_publishedState = value;
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public bool Edited { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public DateTime? PublishDate { get; set; } // set by persistence
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public int? PublisherId { get; set; } // set by persistence
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public int? PublishTemplateId { get; set; } // set by persistence
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public string? PublishName { get; set; } // set by persistence
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<string>? EditedCultures
|
||||
{
|
||||
get => CultureInfos?.Keys.Where(IsCultureEdited);
|
||||
set => _editedCultures = value == null ? null : new HashSet<string>(value, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<string> PublishedCultures => _publishInfos?.Keys ?? [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCulturePublished(string culture)
|
||||
|
||||
// just check _publishInfos
|
||||
// a non-available culture could not become published anyways
|
||||
=> !culture.IsNullOrWhiteSpace() && _publishInfos != null && _publishInfos.ContainsKey(culture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCultureEdited(string culture)
|
||||
=> IsCultureAvailable(culture) && // is available, and
|
||||
(!IsCulturePublished(culture) || // is not published, or
|
||||
(_editedCultures != null && _editedCultures.Contains(culture))); // is edited
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public ContentCultureInfosCollection? PublishCultureInfos
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_publishInfos != null)
|
||||
{
|
||||
return _publishInfos;
|
||||
}
|
||||
|
||||
_publishInfos = new ContentCultureInfosCollection();
|
||||
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
|
||||
return _publishInfos;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_publishInfos != null)
|
||||
{
|
||||
_publishInfos.ClearCollectionChangedEvents();
|
||||
}
|
||||
|
||||
_publishInfos = value;
|
||||
if (_publishInfos != null)
|
||||
{
|
||||
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetPublishName(string? culture)
|
||||
{
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
{
|
||||
return PublishName;
|
||||
}
|
||||
|
||||
if (!ContentType.VariesByCulture())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_publishInfos == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _publishInfos.TryGetValue(culture!, out ContentCultureInfos infos) ? infos.Name : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime? GetPublishDate(string culture)
|
||||
{
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
{
|
||||
return PublishDate;
|
||||
}
|
||||
|
||||
if (!ContentType.VariesByCulture())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_publishInfos == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _publishInfos.TryGetValue(culture, out ContentCultureInfos infos) ? infos.Date : null;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public int PublishedVersionId { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public bool Blueprint { get; set; }
|
||||
|
||||
public override void ResetWereDirtyProperties()
|
||||
{
|
||||
base.ResetWereDirtyProperties();
|
||||
_previousPublishCultureChanges.updatedCultures = null;
|
||||
_previousPublishCultureChanges.removedCultures = null;
|
||||
_previousPublishCultureChanges.addedCultures = null;
|
||||
}
|
||||
|
||||
public override void ResetDirtyProperties(bool rememberDirty)
|
||||
{
|
||||
base.ResetDirtyProperties(rememberDirty);
|
||||
|
||||
if (rememberDirty)
|
||||
{
|
||||
_previousPublishCultureChanges.addedCultures =
|
||||
_currentPublishCultureChanges.addedCultures == null ||
|
||||
_currentPublishCultureChanges.addedCultures.Count == 0
|
||||
? null
|
||||
: new HashSet<string>(_currentPublishCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
_previousPublishCultureChanges.removedCultures =
|
||||
_currentPublishCultureChanges.removedCultures == null ||
|
||||
_currentPublishCultureChanges.removedCultures.Count == 0
|
||||
? null
|
||||
: new HashSet<string>(_currentPublishCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
_previousPublishCultureChanges.updatedCultures =
|
||||
_currentPublishCultureChanges.updatedCultures == null ||
|
||||
_currentPublishCultureChanges.updatedCultures.Count == 0
|
||||
? null
|
||||
: new HashSet<string>(_currentPublishCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
_previousPublishCultureChanges.addedCultures = null;
|
||||
_previousPublishCultureChanges.removedCultures = null;
|
||||
_previousPublishCultureChanges.updatedCultures = null;
|
||||
}
|
||||
|
||||
_currentPublishCultureChanges.addedCultures?.Clear();
|
||||
_currentPublishCultureChanges.removedCultures?.Clear();
|
||||
_currentPublishCultureChanges.updatedCultures?.Clear();
|
||||
|
||||
// take care of the published state
|
||||
_publishedState = _published ? PublishedState.Published : PublishedState.Unpublished;
|
||||
|
||||
if (_publishInfos == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ContentCultureInfos infos in _publishInfos)
|
||||
{
|
||||
infos.ResetDirtyProperties(rememberDirty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Overridden to check special keys.</remarks>
|
||||
public override bool IsPropertyDirty(string propertyName)
|
||||
{
|
||||
// Special check here since we want to check if the request is for changed cultures
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture);
|
||||
return _currentPublishCultureChanges.addedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture);
|
||||
return _currentPublishCultureChanges.removedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture);
|
||||
return _currentPublishCultureChanges.updatedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
return base.IsPropertyDirty(propertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Overridden to check special keys.</remarks>
|
||||
public override bool WasPropertyDirty(string propertyName)
|
||||
{
|
||||
// Special check here since we want to check if the request is for changed cultures
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture);
|
||||
return _previousPublishCultureChanges.addedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture);
|
||||
return _previousPublishCultureChanges.removedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture);
|
||||
return _previousPublishCultureChanges.updatedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
return base.WasPropertyDirty(propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of the current entity with its identity and it's property identities reset
|
||||
/// </summary>
|
||||
@@ -416,152 +131,4 @@ public class Content : ContentBase, IContent
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles culture infos collection changes.
|
||||
/// </summary>
|
||||
private void PublishNamesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(PublishCultureInfos));
|
||||
|
||||
// we don't need to handle other actions, only add/remove, however we could implement Replace and track updated cultures in _updatedCultures too
|
||||
// which would allows us to continue doing WasCulturePublished, but don't think we need it anymore
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
ContentCultureInfos? cultureInfo = e.NewItems?.Cast<ContentCultureInfos>().First();
|
||||
if (_currentPublishCultureChanges.addedCultures == null)
|
||||
{
|
||||
_currentPublishCultureChanges.addedCultures =
|
||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (_currentPublishCultureChanges.updatedCultures == null)
|
||||
{
|
||||
_currentPublishCultureChanges.updatedCultures =
|
||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (cultureInfo is not null)
|
||||
{
|
||||
_currentPublishCultureChanges.addedCultures.Add(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.removedCultures?.Remove(cultureInfo.Culture);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
// Remove listening for changes
|
||||
ContentCultureInfos? cultureInfo = e.OldItems?.Cast<ContentCultureInfos>().First();
|
||||
if (_currentPublishCultureChanges.removedCultures == null)
|
||||
{
|
||||
_currentPublishCultureChanges.removedCultures =
|
||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (cultureInfo is not null)
|
||||
{
|
||||
_currentPublishCultureChanges.removedCultures.Add(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.updatedCultures?.Remove(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.addedCultures?.Remove(cultureInfo.Culture);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
{
|
||||
// Replace occurs when an Update occurs
|
||||
ContentCultureInfos? cultureInfo = e.NewItems?.Cast<ContentCultureInfos>().First();
|
||||
if (_currentPublishCultureChanges.updatedCultures == null)
|
||||
{
|
||||
_currentPublishCultureChanges.updatedCultures =
|
||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (cultureInfo is not null)
|
||||
{
|
||||
_currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the <see cref="ContentType" /> for the current content object
|
||||
/// </summary>
|
||||
/// <param name="contentType">New ContentType for this content</param>
|
||||
/// <remarks>Leaves PropertyTypes intact after change</remarks>
|
||||
internal void ChangeContentType(IContentType contentType) => ChangeContentType(contentType, false);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the <see cref="ContentType" /> for the current content object and removes PropertyTypes,
|
||||
/// which are not part of the new ContentType.
|
||||
/// </summary>
|
||||
/// <param name="contentType">New ContentType for this content</param>
|
||||
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
|
||||
internal void ChangeContentType(IContentType contentType, bool clearProperties)
|
||||
{
|
||||
ChangeContentType(new SimpleContentType(contentType));
|
||||
|
||||
if (clearProperties)
|
||||
{
|
||||
Properties.EnsureCleanPropertyTypes(contentType.CompositionPropertyTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes);
|
||||
}
|
||||
|
||||
Properties.ClearCollectionChangedEvents(); // be sure not to double add
|
||||
Properties.CollectionChanged += PropertiesChanged;
|
||||
}
|
||||
|
||||
protected override void PerformDeepClone(object clone)
|
||||
{
|
||||
base.PerformDeepClone(clone);
|
||||
|
||||
var clonedContent = (Content)clone;
|
||||
|
||||
// TODO: need to reset change tracking bits
|
||||
|
||||
// if culture infos exist then deal with event bindings
|
||||
if (clonedContent._publishInfos != null)
|
||||
{
|
||||
// Clear this event handler if any
|
||||
clonedContent._publishInfos.ClearCollectionChangedEvents();
|
||||
|
||||
// Manually deep clone
|
||||
clonedContent._publishInfos = (ContentCultureInfosCollection?)_publishInfos?.DeepClone();
|
||||
if (clonedContent._publishInfos is not null)
|
||||
{
|
||||
// Re-assign correct event handler
|
||||
clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
clonedContent._currentPublishCultureChanges.updatedCultures = null;
|
||||
clonedContent._currentPublishCultureChanges.addedCultures = null;
|
||||
clonedContent._currentPublishCultureChanges.removedCultures = null;
|
||||
|
||||
clonedContent._previousPublishCultureChanges.updatedCultures = null;
|
||||
clonedContent._previousPublishCultureChanges.addedCultures = null;
|
||||
clonedContent._previousPublishCultureChanges.removedCultures = null;
|
||||
}
|
||||
|
||||
#region Used for change tracking
|
||||
|
||||
private (HashSet<string>? addedCultures, HashSet<string>? removedCultures, HashSet<string>? updatedCultures)
|
||||
_currentPublishCultureChanges;
|
||||
|
||||
private (HashSet<string>? addedCultures, HashSet<string>? removedCultures, HashSet<string>? updatedCultures)
|
||||
_previousPublishCultureChanges;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
public class ElementCreateModel : ContentCreationModelBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
public class ElementCreateResult : ContentCreateResultBase<IElement>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
public class ElementUpdateModel : ContentEditingModelBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
public class ElementUpdateResult : ContentUpdateResultBase<IElement>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
public class ValidateElementUpdateModel : ElementUpdateModel
|
||||
{
|
||||
public ISet<string>? Cultures { get; set; }
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
public sealed class ContentPublishingResult
|
||||
{
|
||||
public IContent? Content { get; init; }
|
||||
public IPublishableContentBase? Content { get; init; }
|
||||
|
||||
public IEnumerable<string> InvalidPropertyAliases { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public static class ContentRepositoryExtensions
|
||||
/// these dates assigned to them differ by a couple of Ticks, but we need to ensure they are persisted at the exact
|
||||
/// same time.
|
||||
/// </remarks>
|
||||
public static void AdjustDates(this IContent content, DateTime date, bool publishing)
|
||||
public static void AdjustDates(this IPublishableContentBase content, DateTime date, bool publishing)
|
||||
{
|
||||
if (content.EditedCultures is not null)
|
||||
{
|
||||
@@ -129,7 +129,7 @@ public static class ContentRepositoryExtensions
|
||||
/// Gets the cultures that have been flagged for unpublishing.
|
||||
/// </summary>
|
||||
/// <remarks>Gets cultures for which content.UnpublishCulture() has been invoked.</remarks>
|
||||
public static IReadOnlyList<string>? GetCulturesUnpublishing(this IContent content)
|
||||
public static IReadOnlyList<string>? GetCulturesUnpublishing(this IPublishableContentBase content)
|
||||
{
|
||||
if (!content.Published || !content.ContentType.VariesByCulture() ||
|
||||
!content.IsPropertyDirty("PublishCultureInfos"))
|
||||
@@ -147,7 +147,7 @@ public static class ContentRepositoryExtensions
|
||||
/// <summary>
|
||||
/// Copies values from another document.
|
||||
/// </summary>
|
||||
public static void CopyFrom(this IContent content, IContent other, string? culture = "*")
|
||||
public static void CopyFrom(this IContent content, IPublishableContentBase other, string? culture = "*")
|
||||
{
|
||||
if (other.ContentTypeId != content.ContentTypeId)
|
||||
{
|
||||
@@ -243,7 +243,7 @@ public static class ContentRepositoryExtensions
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetPublishInfo(this IContent content, string? culture, string? name, DateTime date)
|
||||
public static void SetPublishInfo(this IPublishableContentBase content, string? culture, string? name, DateTime date)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
@@ -273,7 +273,7 @@ public static class ContentRepositoryExtensions
|
||||
}
|
||||
|
||||
// sets the edited cultures on the content
|
||||
public static void SetCultureEdited(this IContent content, IEnumerable<string?>? cultures)
|
||||
public static void SetCultureEdited(this IPublishableContentBase content, IEnumerable<string?>? cultures)
|
||||
{
|
||||
if (cultures == null)
|
||||
{
|
||||
@@ -299,7 +299,7 @@ public static class ContentRepositoryExtensions
|
||||
/// A value indicating whether it was possible to publish the names and values for the specified
|
||||
/// culture(s). The method may fail if required names are not set, but it does NOT validate property data
|
||||
/// </returns>
|
||||
public static bool PublishCulture(this IContent content, CultureImpact? impact, DateTime publishTime, PropertyEditorCollection propertyEditorCollection)
|
||||
public static bool PublishCulture(this IPublishableContentBase content, CultureImpact? impact, DateTime publishTime, PropertyEditorCollection propertyEditorCollection)
|
||||
{
|
||||
if (impact == null)
|
||||
{
|
||||
@@ -368,7 +368,7 @@ public static class ContentRepositoryExtensions
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void PublishPropertyValues(IContent content, IProperty property, string? culture, PropertyEditorCollection propertyEditorCollection)
|
||||
private static void PublishPropertyValues(IPublishableContentBase content, IProperty property, string? culture, PropertyEditorCollection propertyEditorCollection)
|
||||
{
|
||||
// if the content varies by culture, let data editor opt-in to perform partial property publishing (per culture)
|
||||
if (content.ContentType.VariesByCulture()
|
||||
@@ -390,7 +390,7 @@ public static class ContentRepositoryExtensions
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static bool UnpublishCulture(this IContent content, string? culture = "*")
|
||||
public static bool UnpublishCulture(this IPublishableContentBase content, string? culture = "*")
|
||||
{
|
||||
culture = culture?.NullOrWhiteSpaceAsNull();
|
||||
|
||||
@@ -428,7 +428,7 @@ public static class ContentRepositoryExtensions
|
||||
return keepProcessing;
|
||||
}
|
||||
|
||||
public static void ClearPublishInfos(this IContent content) => content.PublishCultureInfos = null;
|
||||
public static void ClearPublishInfos(this IPublishableContentBase content) => content.PublishCultureInfos = null;
|
||||
|
||||
/// <summary>
|
||||
/// Returns false if the culture is already unpublished
|
||||
@@ -436,7 +436,7 @@ public static class ContentRepositoryExtensions
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClearPublishInfo(this IContent content, string? culture)
|
||||
public static bool ClearPublishInfo(this IPublishableContentBase content, string? culture)
|
||||
{
|
||||
if (culture == null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Element object
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public class Element : PublishableContentBase, IElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for creating an Element object
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the element</param>
|
||||
/// <param name="contentType">ContentType for the current Element object</param>
|
||||
/// <param name="culture">An optional culture.</param>
|
||||
public Element(string name, IContentType contentType, string? culture = null)
|
||||
: this(name, contentType, new PropertyCollection(), culture)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating an Element object
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the element</param>
|
||||
/// <param name="contentType">ContentType for the current Element object</param>
|
||||
/// <param name="userId">The identifier of the user creating the Element object</param>
|
||||
/// <param name="culture">An optional culture.</param>
|
||||
public Element(string name, IContentType contentType, int userId, string? culture = null)
|
||||
: this(name, contentType, new PropertyCollection(), culture)
|
||||
{
|
||||
CreatorId = userId;
|
||||
WriterId = userId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating an Element object
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the element</param>
|
||||
/// <param name="contentType">ContentType for the current Element object</param>
|
||||
/// <param name="properties">Collection of properties</param>
|
||||
/// <param name="culture">An optional culture.</param>
|
||||
public Element(string name, IContentType contentType, PropertyCollection properties, string? culture = null)
|
||||
: base(name, Constants.System.Root, contentType, properties, culture)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating an Element object
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the element</param>
|
||||
/// <param name="parentId">Id of the Parent folder</param>
|
||||
/// <param name="contentType">ContentType for the current Element object</param>
|
||||
/// <param name="culture">An optional culture.</param>
|
||||
public Element(string? name, int parentId, IContentType? contentType, string? culture = null)
|
||||
: this(name, parentId, contentType, new PropertyCollection(), culture)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating an Element object
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the element</param>
|
||||
/// <param name="parentId">Id of the Parent folder</param>
|
||||
/// <param name="contentType">ContentType for the current Element object</param>
|
||||
/// <param name="userId">The identifier of the user creating the Element object</param>
|
||||
/// <param name="culture">An optional culture.</param>
|
||||
public Element(string name, int parentId, IContentType contentType, int userId, string? culture = null)
|
||||
: this(name, parentId, contentType, new PropertyCollection(), culture)
|
||||
{
|
||||
CreatorId = userId;
|
||||
WriterId = userId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating an Element object
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the element</param>
|
||||
/// <param name="parentId">Id of the Parent folder</param>
|
||||
/// <param name="contentType">ContentType for the current Element object</param>
|
||||
/// <param name="properties">Collection of properties</param>
|
||||
/// <param name="culture">An optional culture.</param>
|
||||
public Element(string? name, int parentId, IContentType? contentType, PropertyCollection properties, string? culture = null)
|
||||
: base(name, parentId, contentType, properties, culture)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of the current entity with its identity and it's property identities reset
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IElement DeepCloneWithResetIdentities()
|
||||
{
|
||||
var clone = (Element)DeepClone();
|
||||
clone.Key = Guid.Empty;
|
||||
clone.VersionId = clone.PublishedVersionId = 0;
|
||||
clone.ResetIdentity();
|
||||
|
||||
foreach (IProperty property in clone.Properties)
|
||||
{
|
||||
property.ResetIdentity();
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
@@ -3,40 +3,6 @@ namespace Umbraco.Cms.Core.Models.Entities;
|
||||
/// <summary>
|
||||
/// Implements <see cref="IDocumentEntitySlim" />.
|
||||
/// </summary>
|
||||
public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim
|
||||
public class DocumentEntitySlim : PublishableContentEntitySlim, IDocumentEntitySlim
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, string> Empty = new Dictionary<string, string>();
|
||||
|
||||
private IReadOnlyDictionary<string, string>? _cultureNames;
|
||||
private IEnumerable<string>? _editedCultures;
|
||||
private IEnumerable<string>? _publishedCultures;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, string> CultureNames
|
||||
{
|
||||
get => _cultureNames ?? Empty;
|
||||
set => _cultureNames = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> PublishedCultures
|
||||
{
|
||||
get => _publishedCultures ?? Enumerable.Empty<string>();
|
||||
set => _publishedCultures = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> EditedCultures
|
||||
{
|
||||
get => _editedCultures ?? Enumerable.Empty<string>();
|
||||
set => _editedCultures = value;
|
||||
}
|
||||
|
||||
public ContentVariation Variations { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Published { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Edited { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
public class ElementEntitySlim : PublishableContentEntitySlim, IElementEntitySlim
|
||||
{
|
||||
}
|
||||
@@ -3,35 +3,6 @@ namespace Umbraco.Cms.Core.Models.Entities;
|
||||
/// <summary>
|
||||
/// Represents a lightweight document entity, managed by the entity service.
|
||||
/// </summary>
|
||||
public interface IDocumentEntitySlim : IContentEntitySlim
|
||||
public interface IDocumentEntitySlim : IPublishableContentEntitySlim
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the variant name for each culture
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, string> CultureNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published cultures.
|
||||
/// </summary>
|
||||
IEnumerable<string> PublishedCultures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the edited cultures.
|
||||
/// </summary>
|
||||
IEnumerable<string> EditedCultures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content variation of the content type.
|
||||
/// </summary>
|
||||
ContentVariation Variations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is published.
|
||||
/// </summary>
|
||||
bool Published { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content has been edited.
|
||||
/// </summary>
|
||||
bool Edited { get; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
public interface IElementEntitySlim : IPublishableContentEntitySlim
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
public interface IPublishableContentEntitySlim : IContentEntitySlim
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the variant name for each culture
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, string> CultureNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published cultures.
|
||||
/// </summary>
|
||||
IEnumerable<string> PublishedCultures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the edited cultures.
|
||||
/// </summary>
|
||||
IEnumerable<string> EditedCultures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content variation of the content type.
|
||||
/// </summary>
|
||||
ContentVariation Variations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is published.
|
||||
/// </summary>
|
||||
bool Published { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content has been edited.
|
||||
/// </summary>
|
||||
bool Edited { get; }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
public abstract class PublishableContentEntitySlim : ContentEntitySlim
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, string> Empty = new Dictionary<string, string>();
|
||||
|
||||
private IReadOnlyDictionary<string, string>? _cultureNames;
|
||||
private IEnumerable<string>? _editedCultures;
|
||||
private IEnumerable<string>? _publishedCultures;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, string> CultureNames
|
||||
{
|
||||
get => _cultureNames ?? Empty;
|
||||
set => _cultureNames = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> PublishedCultures
|
||||
{
|
||||
get => _publishedCultures ?? Enumerable.Empty<string>();
|
||||
set => _publishedCultures = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> EditedCultures
|
||||
{
|
||||
get => _editedCultures ?? Enumerable.Empty<string>();
|
||||
set => _editedCultures = value;
|
||||
}
|
||||
|
||||
public ContentVariation Variations { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Published { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Edited { get; set; }
|
||||
}
|
||||
@@ -13,6 +13,7 @@ public sealed class EntityContainer : TreeEntityBase, IUmbracoEntity
|
||||
{ Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentTypeContainer },
|
||||
{ Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MediaTypeContainer },
|
||||
{ Constants.ObjectTypes.DocumentBlueprint, Constants.ObjectTypes.DocumentBlueprintContainer },
|
||||
{ Constants.ObjectTypes.Element, Constants.ObjectTypes.ElementContainer },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,35 +6,13 @@ namespace Umbraco.Cms.Core.Models;
|
||||
/// <remarks>
|
||||
/// <para>A document can be published, rendered by a template.</para>
|
||||
/// </remarks>
|
||||
public interface IContent : IContentBase
|
||||
public interface IContent : IPublishableContentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the template id used to render the content.
|
||||
/// </summary>
|
||||
int? TemplateId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is published.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="PublishedVersionId" /> property tells you which version of the content is currently published.</remarks>
|
||||
bool Published { get; set; }
|
||||
|
||||
PublishedState PublishedState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content has been edited.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Will return `true` once unpublished edits have been made after the version with
|
||||
/// <see cref="PublishedVersionId" /> has been published.
|
||||
/// </remarks>
|
||||
bool Edited { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version identifier for the currently published version of the content.
|
||||
/// </summary>
|
||||
int PublishedVersionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content item is a blueprint.
|
||||
/// </summary>
|
||||
@@ -46,90 +24,6 @@ public interface IContent : IContentBase
|
||||
/// <remarks>When editing the content, the template can change, but this will not until the content is published.</remarks>
|
||||
int? PublishTemplateId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the published version of the content.
|
||||
/// </summary>
|
||||
/// <remarks>When editing the content, the name can change, but this will not until the content is published.</remarks>
|
||||
string? PublishName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the user who published the content.
|
||||
/// </summary>
|
||||
int? PublisherId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date and time the content was published.
|
||||
/// </summary>
|
||||
DateTime? PublishDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published culture infos of the content.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Because a dictionary key cannot be <c>null</c> this cannot get the invariant
|
||||
/// name, which must be get via the <see cref="PublishName" /> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
ContentCultureInfosCollection? PublishCultureInfos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published cultures.
|
||||
/// </summary>
|
||||
IEnumerable<string> PublishedCultures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the edited cultures.
|
||||
/// </summary>
|
||||
IEnumerable<string>? EditedCultures { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a culture is published.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A culture becomes published whenever values for this culture are published,
|
||||
/// and the content published name for this culture is non-null. It becomes non-published
|
||||
/// whenever values for this culture are unpublished.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A culture becomes published as soon as PublishCulture has been invoked,
|
||||
/// even though the document might not have been saved yet (and can have no identity).
|
||||
/// </para>
|
||||
/// <para>Does not support the '*' wildcard (returns false).</para>
|
||||
/// </remarks>
|
||||
bool IsCulturePublished(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date a culture was published.
|
||||
/// </summary>
|
||||
DateTime? GetPublishDate(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicated whether a given culture is edited.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A culture is edited when it is available, and not published or published but
|
||||
/// with changes.
|
||||
/// </para>
|
||||
/// <para>A culture can be edited even though the document might now have been saved yet (and can have no identity).</para>
|
||||
/// <para>Does not support the '*' wildcard (returns false).</para>
|
||||
/// </remarks>
|
||||
bool IsCultureEdited(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the published version of the content for a given culture.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When editing the content, the name can change, but this will not until the content is published.</para>
|
||||
/// <para>
|
||||
/// When <paramref name="culture" /> is <c>null</c>, gets the invariant
|
||||
/// language, which is the value of the <see cref="PublishName" /> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
string? GetPublishName(string? culture);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
public interface IElement : IPublishableContentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IElement DeepCloneWithResetIdentities();
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
public interface IPublishableContentBase : IContentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is published.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="PublishedVersionId" /> property tells you which version of the content is currently published.</remarks>
|
||||
bool Published { get; set; }
|
||||
|
||||
PublishedState PublishedState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content has been edited.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Will return `true` once unpublished edits have been made after the version with
|
||||
/// <see cref="PublishedVersionId" /> has been published.
|
||||
/// </remarks>
|
||||
bool Edited { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version identifier for the currently published version of the content.
|
||||
/// </summary>
|
||||
int PublishedVersionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the published version of the content.
|
||||
/// </summary>
|
||||
/// <remarks>When editing the content, the name can change, but this will not until the content is published.</remarks>
|
||||
string? PublishName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the user who published the content.
|
||||
/// </summary>
|
||||
int? PublisherId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date and time the content was published.
|
||||
/// </summary>
|
||||
DateTime? PublishDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published culture infos of the content.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Because a dictionary key cannot be <c>null</c> this cannot get the invariant
|
||||
/// name, which must be get via the <see cref="PublishName" /> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
ContentCultureInfosCollection? PublishCultureInfos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published cultures.
|
||||
/// </summary>
|
||||
IEnumerable<string> PublishedCultures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the edited cultures.
|
||||
/// </summary>
|
||||
IEnumerable<string>? EditedCultures { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a culture is published.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A culture becomes published whenever values for this culture are published,
|
||||
/// and the content published name for this culture is non-null. It becomes non-published
|
||||
/// whenever values for this culture are unpublished.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A culture becomes published as soon as PublishCulture has been invoked,
|
||||
/// even though the document might not have been saved yet (and can have no identity).
|
||||
/// </para>
|
||||
/// <para>Does not support the '*' wildcard (returns false).</para>
|
||||
/// </remarks>
|
||||
bool IsCulturePublished(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date a culture was published.
|
||||
/// </summary>
|
||||
DateTime? GetPublishDate(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicated whether a given culture is edited.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A culture is edited when it is available, and not published or published but
|
||||
/// with changes.
|
||||
/// </para>
|
||||
/// <para>A culture can be edited even though the document might now have been saved yet (and can have no identity).</para>
|
||||
/// <para>Does not support the '*' wildcard (returns false).</para>
|
||||
/// </remarks>
|
||||
bool IsCultureEdited(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the published version of the content for a given culture.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When editing the content, the name can change, but this will not until the content is published.</para>
|
||||
/// <para>
|
||||
/// When <paramref name="culture" /> is <c>null</c>, gets the invariant
|
||||
/// language, which is the value of the <see cref="PublishName" /> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
string? GetPublishName(string? culture);
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
using System.Collections.Specialized;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
// TODO ELEMENTS: ensure property annotations ect. are up to date from Content
|
||||
public abstract class PublishableContentBase : ContentBase, IPublishableContentBase
|
||||
{
|
||||
private HashSet<string>? _editedCultures;
|
||||
private bool _published;
|
||||
private PublishedState _publishedState;
|
||||
private ContentCultureInfosCollection? _publishInfos;
|
||||
|
||||
protected PublishableContentBase(string? name, int parentId, IContentTypeComposition? contentType, IPropertyCollection properties, string? culture = null)
|
||||
: base(name, parentId, contentType, properties, culture)
|
||||
{
|
||||
if (contentType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contentType));
|
||||
}
|
||||
|
||||
_publishedState = PublishedState.Unpublished;
|
||||
PublishedVersionId = 0;
|
||||
}
|
||||
|
||||
protected PublishableContentBase(string? name, IContentBase? parent, IContentTypeComposition contentType, IPropertyCollection properties, string? culture = null)
|
||||
: base(name, parent, contentType, properties, culture)
|
||||
{
|
||||
if (contentType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contentType));
|
||||
}
|
||||
|
||||
_publishedState = PublishedState.Unpublished;
|
||||
PublishedVersionId = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this content item is published or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// the setter is should only be invoked from
|
||||
/// - the ContentFactory when creating a content entity from a dto
|
||||
/// - the ContentRepository when updating a content entity
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public bool Published
|
||||
{
|
||||
get => _published;
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(value, ref _published, nameof(Published));
|
||||
_publishedState = _published ? PublishedState.Published : PublishedState.Unpublished;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published state of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The state should be Published or Unpublished, depending on whether Published
|
||||
/// is true or false, but can also temporarily be Publishing or Unpublishing when the
|
||||
/// content item is about to be saved.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public PublishedState PublishedState
|
||||
{
|
||||
get => _publishedState;
|
||||
set
|
||||
{
|
||||
if (value != PublishedState.Publishing && value != PublishedState.Unpublishing)
|
||||
{
|
||||
throw new ArgumentException("Invalid state, only Publishing and Unpublishing are accepted.");
|
||||
}
|
||||
|
||||
_publishedState = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[IgnoreDataMember]
|
||||
public bool Edited { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public DateTime? PublishDate { get; set; } // set by persistence
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public int? PublisherId { get; set; } // set by persistence
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public string? PublishName { get; set; } // set by persistence
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<string>? EditedCultures
|
||||
{
|
||||
get => CultureInfos?.Keys.Where(IsCultureEdited);
|
||||
set => _editedCultures = value == null ? null : new HashSet<string>(value, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<string> PublishedCultures => _publishInfos?.Keys ?? [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCulturePublished(string culture)
|
||||
|
||||
// just check _publishInfos
|
||||
// a non-available culture could not become published anyways
|
||||
=> !culture.IsNullOrWhiteSpace() && _publishInfos != null && _publishInfos.ContainsKey(culture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCultureEdited(string culture)
|
||||
=> IsCultureAvailable(culture) && // is available, and
|
||||
(!IsCulturePublished(culture) || // is not published, or
|
||||
(_editedCultures != null && _editedCultures.Contains(culture))); // is edited
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public ContentCultureInfosCollection? PublishCultureInfos
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_publishInfos != null)
|
||||
{
|
||||
return _publishInfos;
|
||||
}
|
||||
|
||||
_publishInfos = new ContentCultureInfosCollection();
|
||||
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
|
||||
return _publishInfos;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_publishInfos != null)
|
||||
{
|
||||
_publishInfos.ClearCollectionChangedEvents();
|
||||
}
|
||||
|
||||
_publishInfos = value;
|
||||
if (_publishInfos != null)
|
||||
{
|
||||
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetPublishName(string? culture)
|
||||
{
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
{
|
||||
return PublishName;
|
||||
}
|
||||
|
||||
if (!ContentType.VariesByCulture())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_publishInfos == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _publishInfos.TryGetValue(culture!, out ContentCultureInfos infos) ? infos.Name : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime? GetPublishDate(string culture)
|
||||
{
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
{
|
||||
return PublishDate;
|
||||
}
|
||||
|
||||
if (!ContentType.VariesByCulture())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_publishInfos == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _publishInfos.TryGetValue(culture, out ContentCultureInfos infos) ? infos.Date : null;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public int PublishedVersionId { get; set; }
|
||||
|
||||
public override void ResetWereDirtyProperties()
|
||||
{
|
||||
base.ResetWereDirtyProperties();
|
||||
_previousPublishCultureChanges.updatedCultures = null;
|
||||
_previousPublishCultureChanges.removedCultures = null;
|
||||
_previousPublishCultureChanges.addedCultures = null;
|
||||
}
|
||||
|
||||
public override void ResetDirtyProperties(bool rememberDirty)
|
||||
{
|
||||
base.ResetDirtyProperties(rememberDirty);
|
||||
|
||||
if (rememberDirty)
|
||||
{
|
||||
_previousPublishCultureChanges.addedCultures =
|
||||
_currentPublishCultureChanges.addedCultures == null ||
|
||||
_currentPublishCultureChanges.addedCultures.Count == 0
|
||||
? null
|
||||
: new HashSet<string>(_currentPublishCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
_previousPublishCultureChanges.removedCultures =
|
||||
_currentPublishCultureChanges.removedCultures == null ||
|
||||
_currentPublishCultureChanges.removedCultures.Count == 0
|
||||
? null
|
||||
: new HashSet<string>(_currentPublishCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
_previousPublishCultureChanges.updatedCultures =
|
||||
_currentPublishCultureChanges.updatedCultures == null ||
|
||||
_currentPublishCultureChanges.updatedCultures.Count == 0
|
||||
? null
|
||||
: new HashSet<string>(_currentPublishCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
_previousPublishCultureChanges.addedCultures = null;
|
||||
_previousPublishCultureChanges.removedCultures = null;
|
||||
_previousPublishCultureChanges.updatedCultures = null;
|
||||
}
|
||||
|
||||
_currentPublishCultureChanges.addedCultures?.Clear();
|
||||
_currentPublishCultureChanges.removedCultures?.Clear();
|
||||
_currentPublishCultureChanges.updatedCultures?.Clear();
|
||||
|
||||
// take care of the published state
|
||||
_publishedState = _published ? PublishedState.Published : PublishedState.Unpublished;
|
||||
|
||||
if (_publishInfos == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ContentCultureInfos infos in _publishInfos)
|
||||
{
|
||||
infos.ResetDirtyProperties(rememberDirty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Overridden to check special keys.</remarks>
|
||||
public override bool IsPropertyDirty(string propertyName)
|
||||
{
|
||||
// Special check here since we want to check if the request is for changed cultures
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture);
|
||||
return _currentPublishCultureChanges.addedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture);
|
||||
return _currentPublishCultureChanges.removedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture);
|
||||
return _currentPublishCultureChanges.updatedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
return base.IsPropertyDirty(propertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Overridden to check special keys.</remarks>
|
||||
public override bool WasPropertyDirty(string propertyName)
|
||||
{
|
||||
// Special check here since we want to check if the request is for changed cultures
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture);
|
||||
return _previousPublishCultureChanges.addedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture);
|
||||
return _previousPublishCultureChanges.removedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture))
|
||||
{
|
||||
var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture);
|
||||
return _previousPublishCultureChanges.updatedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
return base.WasPropertyDirty(propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles culture infos collection changes.
|
||||
/// </summary>
|
||||
private void PublishNamesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(PublishCultureInfos));
|
||||
|
||||
// we don't need to handle other actions, only add/remove, however we could implement Replace and track updated cultures in _updatedCultures too
|
||||
// which would allows us to continue doing WasCulturePublished, but don't think we need it anymore
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
ContentCultureInfos? cultureInfo = e.NewItems?.Cast<ContentCultureInfos>().First();
|
||||
if (_currentPublishCultureChanges.addedCultures == null)
|
||||
{
|
||||
_currentPublishCultureChanges.addedCultures =
|
||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (_currentPublishCultureChanges.updatedCultures == null)
|
||||
{
|
||||
_currentPublishCultureChanges.updatedCultures =
|
||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (cultureInfo is not null)
|
||||
{
|
||||
_currentPublishCultureChanges.addedCultures.Add(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.removedCultures?.Remove(cultureInfo.Culture);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
// Remove listening for changes
|
||||
ContentCultureInfos? cultureInfo = e.OldItems?.Cast<ContentCultureInfos>().First();
|
||||
if (_currentPublishCultureChanges.removedCultures == null)
|
||||
{
|
||||
_currentPublishCultureChanges.removedCultures =
|
||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (cultureInfo is not null)
|
||||
{
|
||||
_currentPublishCultureChanges.removedCultures.Add(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.updatedCultures?.Remove(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.addedCultures?.Remove(cultureInfo.Culture);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
{
|
||||
// Replace occurs when an Update occurs
|
||||
ContentCultureInfos? cultureInfo = e.NewItems?.Cast<ContentCultureInfos>().First();
|
||||
if (_currentPublishCultureChanges.updatedCultures == null)
|
||||
{
|
||||
_currentPublishCultureChanges.updatedCultures =
|
||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (cultureInfo is not null)
|
||||
{
|
||||
_currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the <see cref="ContentType" /> for the current content object
|
||||
/// </summary>
|
||||
/// <param name="contentType">New ContentType for this content</param>
|
||||
/// <remarks>Leaves PropertyTypes intact after change</remarks>
|
||||
internal void ChangeContentType(IContentType contentType) => ChangeContentType(contentType, false);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the <see cref="ContentType" /> for the current content object and removes PropertyTypes,
|
||||
/// which are not part of the new ContentType.
|
||||
/// </summary>
|
||||
/// <param name="contentType">New ContentType for this content</param>
|
||||
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
|
||||
internal void ChangeContentType(IContentType contentType, bool clearProperties)
|
||||
{
|
||||
ChangeContentType(new SimpleContentType(contentType));
|
||||
|
||||
if (clearProperties)
|
||||
{
|
||||
Properties.EnsureCleanPropertyTypes(contentType.CompositionPropertyTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes);
|
||||
}
|
||||
|
||||
Properties.ClearCollectionChangedEvents(); // be sure not to double add
|
||||
Properties.CollectionChanged += PropertiesChanged;
|
||||
}
|
||||
|
||||
protected override void PerformDeepClone(object clone)
|
||||
{
|
||||
base.PerformDeepClone(clone);
|
||||
|
||||
var clonedContent = (PublishableContentBase)clone;
|
||||
|
||||
// TODO: need to reset change tracking bits
|
||||
|
||||
// if culture infos exist then deal with event bindings
|
||||
if (clonedContent._publishInfos != null)
|
||||
{
|
||||
// Clear this event handler if any
|
||||
clonedContent._publishInfos.ClearCollectionChangedEvents();
|
||||
|
||||
// Manually deep clone
|
||||
clonedContent._publishInfos = (ContentCultureInfosCollection?)_publishInfos?.DeepClone();
|
||||
if (clonedContent._publishInfos is not null)
|
||||
{
|
||||
// Re-assign correct event handler
|
||||
clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
clonedContent._currentPublishCultureChanges.updatedCultures = null;
|
||||
clonedContent._currentPublishCultureChanges.addedCultures = null;
|
||||
clonedContent._currentPublishCultureChanges.removedCultures = null;
|
||||
|
||||
clonedContent._previousPublishCultureChanges.updatedCultures = null;
|
||||
clonedContent._previousPublishCultureChanges.addedCultures = null;
|
||||
clonedContent._previousPublishCultureChanges.removedCultures = null;
|
||||
}
|
||||
|
||||
#region Used for change tracking
|
||||
|
||||
private (HashSet<string>? addedCultures, HashSet<string>? removedCultures, HashSet<string>? updatedCultures)
|
||||
_currentPublishCultureChanges;
|
||||
|
||||
private (HashSet<string>? addedCultures, HashSet<string>? removedCultures, HashSet<string>? updatedCultures)
|
||||
_previousPublishCultureChanges;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -9,31 +9,28 @@ namespace Umbraco.Cms.Core.Models.PublishedContent;
|
||||
/// </remarks>
|
||||
public interface IPublishedContent : IPublishedElement
|
||||
{
|
||||
// TODO: IPublishedContent properties colliding with models
|
||||
// we need to find a way to remove as much clutter as possible from IPublishedContent,
|
||||
// since this is preventing someone from creating a property named 'Path' and have it
|
||||
// in a model, for instance. we could move them all under one unique property eg
|
||||
// Infos, so we would do .Infos.SortOrder - just an idea - not going to do it in v8
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the content item.
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the content item for the current culture.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL segment of the content item for the current culture.
|
||||
/// </summary>
|
||||
string? UrlSegment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sort order of the content item.
|
||||
/// Gets the identifier of the template to use to render the content item.
|
||||
/// </summary>
|
||||
int SortOrder { get; }
|
||||
int? TemplateId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>The parent of root content is <c>null</c>.</remarks>
|
||||
[Obsolete("Please use either the IPublishedContent.Parent<>() extension method in the Umbraco.Extensions namespace, or IDocumentNavigationQueryService if you only need keys. Scheduled for removal in Umbraco 18.")]
|
||||
IPublishedContent? Parent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children of the content item that are available for the current culture.
|
||||
/// </summary>
|
||||
[Obsolete("Please use either the IPublishedContent.Children() extension method in the Umbraco.Extensions namespace, or IDocumentNavigationQueryService if you only need keys. Scheduled for removal in Umbraco 18.")]
|
||||
IEnumerable<IPublishedContent> Children { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tree level of the content item.
|
||||
@@ -44,104 +41,4 @@ public interface IPublishedContent : IPublishedElement
|
||||
/// Gets the tree path of the content item.
|
||||
/// </summary>
|
||||
string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the template to use to render the content item.
|
||||
/// </summary>
|
||||
int? TemplateId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the user who created the content item.
|
||||
/// </summary>
|
||||
int CreatorId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date the content item was created.
|
||||
/// </summary>
|
||||
DateTime CreateDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the user who last updated the content item.
|
||||
/// </summary>
|
||||
int WriterId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date the content item was last updated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>For published content items, this is also the date the item was published.</para>
|
||||
/// <para>
|
||||
/// This date is always global to the content item, see CultureDate() for the
|
||||
/// date each culture was published.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
DateTime UpdateDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets available culture infos.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Contains only those culture that are available. For a published content, these are
|
||||
/// the cultures that are published. For a draft content, those that are 'available' ie
|
||||
/// have a non-empty content name.
|
||||
/// </para>
|
||||
/// <para>Does not contain the invariant culture.</para>
|
||||
/// // TODO?
|
||||
/// </remarks>
|
||||
IReadOnlyDictionary<string, PublishedCultureInfo> Cultures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the content item (document, media...).
|
||||
/// </summary>
|
||||
PublishedItemType ItemType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>The parent of root content is <c>null</c>.</remarks>
|
||||
[Obsolete("Please use either the IPublishedContent.Parent<>() extension method in the Umbraco.Extensions namespace, or IDocumentNavigationQueryService if you only need keys. Scheduled for removal in Umbraco 18.")]
|
||||
IPublishedContent? Parent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is draft.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A content is draft when it is the unpublished version of a content, which may
|
||||
/// have a published version, or not.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When retrieving documents from cache in non-preview mode, IsDraft is always false,
|
||||
/// as only published documents are returned. When retrieving in preview mode, IsDraft can
|
||||
/// either be true (document is not published, or has been edited, and what is returned
|
||||
/// is the edited version) or false (document is published, and has not been edited, and
|
||||
/// what is returned is the published version).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
bool IsDraft(string? culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is published.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>A content is published when it has a published version.</para>
|
||||
/// <para>
|
||||
/// When retrieving documents from cache in non-preview mode, IsPublished is always
|
||||
/// true, as only published documents are returned. When retrieving in draft mode, IsPublished
|
||||
/// can either be true (document has a published version) or false (document has no
|
||||
/// published version).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It is therefore possible for both IsDraft and IsPublished to be true at the same
|
||||
/// time, meaning that the content is the draft version, and a published version exists.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
bool IsPublished(string? culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children of the content item that are available for the current culture.
|
||||
/// </summary>
|
||||
[Obsolete("Please use either the IPublishedContent.Children() extension method in the Umbraco.Extensions namespace, or IDocumentNavigationQueryService if you only need keys. Scheduled for removal in Umbraco 18.")]
|
||||
IEnumerable<IPublishedContent> Children { get; }
|
||||
}
|
||||
|
||||
@@ -47,4 +47,101 @@ public interface IPublishedElement
|
||||
IPublishedProperty? GetProperty(string alias);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the content item.
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the content item for the current culture.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sort order of the content item.
|
||||
/// </summary>
|
||||
int SortOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the user who created the content item.
|
||||
/// </summary>
|
||||
int CreatorId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date the content item was created.
|
||||
/// </summary>
|
||||
DateTime CreateDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the user who last updated the content item.
|
||||
/// </summary>
|
||||
int WriterId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date the content item was last updated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>For published content items, this is also the date the item was published.</para>
|
||||
/// <para>
|
||||
/// This date is always global to the content item, see CultureDate() for the
|
||||
/// date each culture was published.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
DateTime UpdateDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets available culture infos.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Contains only those culture that are available. For a published content, these are
|
||||
/// the cultures that are published. For a draft content, those that are 'available' ie
|
||||
/// have a non-empty content name.
|
||||
/// </para>
|
||||
/// <para>Does not contain the invariant culture.</para>
|
||||
/// // TODO?
|
||||
/// </remarks>
|
||||
IReadOnlyDictionary<string, PublishedCultureInfo> Cultures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the content item (document, media...).
|
||||
/// </summary>
|
||||
PublishedItemType ItemType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is draft.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A content is draft when it is the unpublished version of a content, which may
|
||||
/// have a published version, or not.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When retrieving documents from cache in non-preview mode, IsDraft is always false,
|
||||
/// as only published documents are returned. When retrieving in preview mode, IsDraft can
|
||||
/// either be true (document is not published, or has been edited, and what is returned
|
||||
/// is the edited version) or false (document is published, and has not been edited, and
|
||||
/// what is returned is the published version).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
bool IsDraft(string? culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is published.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>A content is published when it has a published version.</para>
|
||||
/// <para>
|
||||
/// When retrieving documents from cache in non-preview mode, IsPublished is always
|
||||
/// true, as only published documents are returned. When retrieving in draft mode, IsPublished
|
||||
/// can either be true (document has a published version) or false (document has no
|
||||
/// published version).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It is therefore possible for both IsDraft and IsPublished to be true at the same
|
||||
/// time, meaning that the content is the draft version, and a published version exists.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
bool IsPublished(string? culture = null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
/// <summary>
|
||||
/// Provide an abstract base class for publishable content implementations (like <c>IPublishedContent</c> and <c>IPublishedElement</c> implementations).
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Content Id: {Id}")]
|
||||
public abstract class PublishableContentBase
|
||||
{
|
||||
public abstract IPublishedContentType ContentType { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Guid Key { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int Id { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int SortOrder { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int CreatorId { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract DateTime CreateDate { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int WriterId { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract DateTime UpdateDate { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IReadOnlyDictionary<string, PublishedCultureInfo> Cultures { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract PublishedItemType ItemType { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool IsDraft(string? culture = null);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool IsPublished(string? culture = null);
|
||||
|
||||
/// <inheritdoc cref="IPublishedElement.Properties"/>
|
||||
public abstract IEnumerable<IPublishedProperty> Properties { get; }
|
||||
|
||||
/// <inheritdoc cref="IPublishedElement.GetProperty(string)"/>
|
||||
public abstract IPublishedProperty? GetProperty(string alias);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -12,21 +10,14 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
/// </summary>
|
||||
/// <remarks>This base class does which (a) consistently resolves and caches the URL, (b) provides an implementation
|
||||
/// for this[alias], and (c) provides basic content set management.</remarks>
|
||||
[DebuggerDisplay("Content Id: {Id}")]
|
||||
public abstract class PublishedContentBase : IPublishedContent
|
||||
// TODO ELEMENTS: correct version for the obsolete message here
|
||||
[Obsolete("Please implement PublishableContentBase instead. Scheduled for removal in VXX")]
|
||||
public abstract class PublishedContentBase : PublishableContentBase, IPublishedContent
|
||||
{
|
||||
private readonly IVariationContextAccessor? _variationContextAccessor;
|
||||
|
||||
protected PublishedContentBase(IVariationContextAccessor? variationContextAccessor) => _variationContextAccessor = variationContextAccessor;
|
||||
|
||||
public abstract IPublishedContentType ContentType { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Guid Key { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int Id { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Name => this.Name(_variationContextAccessor);
|
||||
|
||||
@@ -34,9 +25,6 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
[Obsolete("Please use GetUrlSegment() on IDocumentUrlService instead. Scheduled for removal in V16.")]
|
||||
public virtual string? UrlSegment => this.UrlSegment(_variationContextAccessor);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int SortOrder { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Not supported for members, scheduled for removal in v17")]
|
||||
public abstract int Level { get; }
|
||||
@@ -48,30 +36,6 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
/// <inheritdoc />
|
||||
public abstract int? TemplateId { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int CreatorId { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract DateTime CreateDate { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int WriterId { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract DateTime UpdateDate { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IReadOnlyDictionary<string, PublishedCultureInfo> Cultures { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract PublishedItemType ItemType { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool IsDraft(string? culture = null);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool IsPublished(string? culture = null);
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Please use TryGetParentKey() on IDocumentNavigationQueryService or IMediaNavigationQueryService instead. Scheduled for removal in V16.")]
|
||||
public abstract IPublishedContent? Parent { get; }
|
||||
@@ -80,13 +44,6 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
[Obsolete("Please use TryGetChildrenKeys() on IDocumentNavigationQueryService or IMediaNavigationQueryService instead. Scheduled for removal in V16.")]
|
||||
public virtual IEnumerable<IPublishedContent> Children => GetChildren();
|
||||
|
||||
|
||||
/// <inheritdoc cref="IPublishedElement.Properties"/>
|
||||
public abstract IEnumerable<IPublishedProperty> Properties { get; }
|
||||
|
||||
/// <inheritdoc cref="IPublishedElement.GetProperty(string)"/>
|
||||
public abstract IPublishedProperty? GetProperty(string alias);
|
||||
|
||||
private IEnumerable<IPublishedContent> GetChildren()
|
||||
{
|
||||
INavigationQueryService? navigationQueryService;
|
||||
|
||||
@@ -16,6 +16,23 @@ public static class PublishedContentExtensionsForModels
|
||||
public static IPublishedContent? CreateModel(
|
||||
this IPublishedContent? content,
|
||||
IPublishedModelFactory? publishedModelFactory)
|
||||
=> CreateModel<IPublishedContent>(content, publishedModelFactory);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a strongly typed published content model for an internal published element.
|
||||
/// </summary>
|
||||
/// <param name="element">The internal published element.</param>
|
||||
/// <param name="publishedModelFactory">The published model factory</param>
|
||||
/// <returns>The strongly typed published element model.</returns>
|
||||
public static IPublishedElement? CreateModel(
|
||||
this IPublishedElement? element,
|
||||
IPublishedModelFactory? publishedModelFactory)
|
||||
=> CreateModel<IPublishedElement>(element, publishedModelFactory);
|
||||
|
||||
private static T? CreateModel<T>(
|
||||
IPublishedElement? content,
|
||||
IPublishedModelFactory? publishedModelFactory)
|
||||
where T : IPublishedElement
|
||||
{
|
||||
if (publishedModelFactory == null)
|
||||
{
|
||||
@@ -24,7 +41,7 @@ public static class PublishedContentExtensionsForModels
|
||||
|
||||
if (content == null)
|
||||
{
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
|
||||
// get model
|
||||
@@ -36,10 +53,10 @@ public static class PublishedContentExtensionsForModels
|
||||
}
|
||||
|
||||
// if factory returns a different type, throw
|
||||
if (!(model is IPublishedContent publishedContent))
|
||||
if (!(model is T publishedContent))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Factory returned model of type {model.GetType().FullName} which does not implement IPublishedContent.");
|
||||
$"Factory returned model of type {model.GetType().FullName} which does not implement {typeof(T).Name}.");
|
||||
}
|
||||
|
||||
return publishedContent;
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent;
|
||||
/// wrap and extend another <c>IPublishedContent</c>.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{Id}: {Name} ({ContentType?.Alias})")]
|
||||
// TODO ELEMENTS: this should probably inherit PublishedElementWrapped, instead of all this code duplication
|
||||
public abstract class PublishedContentWrapped : IPublishedContent
|
||||
{
|
||||
private readonly IPublishedContent _content;
|
||||
|
||||
@@ -33,6 +33,28 @@ public abstract class PublishedElementWrapped : IPublishedElement
|
||||
/// <inheritdoc />
|
||||
public IPublishedProperty? GetProperty(string alias) => _content.GetProperty(alias);
|
||||
|
||||
public int Id => _content.Id;
|
||||
|
||||
public string Name => _content.Name;
|
||||
|
||||
public int SortOrder => _content.SortOrder;
|
||||
|
||||
public int CreatorId => _content.CreatorId;
|
||||
|
||||
public DateTime CreateDate => _content.CreateDate;
|
||||
|
||||
public int WriterId => _content.WriterId;
|
||||
|
||||
public DateTime UpdateDate => _content.UpdateDate;
|
||||
|
||||
public IReadOnlyDictionary<string, PublishedCultureInfo> Cultures => _content.Cultures;
|
||||
|
||||
public PublishedItemType ItemType => _content.ItemType;
|
||||
|
||||
public bool IsDraft(string? culture = null) => _content.IsDraft(culture);
|
||||
|
||||
public bool IsPublished(string? culture = null) => _content.IsPublished(culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the wrapped content.
|
||||
/// </summary>
|
||||
|
||||
@@ -180,4 +180,20 @@ public enum UmbracoObjectTypes
|
||||
[UmbracoObjectType(Constants.ObjectTypes.Strings.IdReservation)]
|
||||
[FriendlyName("Identifier Reservation")]
|
||||
IdReservation,
|
||||
|
||||
/// <summary>
|
||||
/// Element
|
||||
/// </summary>
|
||||
[UmbracoObjectType(Constants.ObjectTypes.Strings.Element, typeof(IElement))]
|
||||
[FriendlyName("Element")]
|
||||
[UmbracoUdiType(Constants.UdiEntityType.Element)]
|
||||
Element,
|
||||
|
||||
/// <summary>
|
||||
/// Element container.
|
||||
/// </summary>
|
||||
[UmbracoObjectType(Constants.ObjectTypes.Strings.ElementContainer)]
|
||||
[FriendlyName("Element Container")]
|
||||
[UmbracoUdiType(Constants.UdiEntityType.ElementContainer)]
|
||||
ElementContainer,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Umbraco.Cms.Core.Sync;
|
||||
|
||||
namespace Umbraco.Cms.Core.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// A notification that is used to trigger the Element Cache Refresher.
|
||||
/// </summary>
|
||||
public class ElementCacheRefresherNotification : CacheRefresherNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ElementCacheRefresherNotification"/>
|
||||
/// </summary>
|
||||
/// <param name="messageObject">
|
||||
/// The refresher payload.
|
||||
/// </param>
|
||||
/// <param name="messageType">
|
||||
/// Type of the cache refresher message, <see cref="MessageType"/>
|
||||
/// </param>
|
||||
public ElementCacheRefresherNotification(object messageObject, MessageType messageType)
|
||||
: base(messageObject, messageType)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// The notification is published after the element has been copied.
|
||||
/// </summary>
|
||||
public sealed class ElementCopiedNotification : CopiedNotification<IElement>
|
||||
{
|
||||
public ElementCopiedNotification(IElement original, IElement copy, int parentId, Guid? parentKey, bool relateToOriginal, EventMessages messages)
|
||||
: base(original, copy, parentId, parentKey, relateToOriginal, messages)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// The notification is published after a copy object has been created and had its parentId updated.
|
||||
/// </summary>
|
||||
public sealed class ElementCopyingNotification : CopyingNotification<IElement>
|
||||
{
|
||||
public ElementCopyingNotification(IElement original, IElement copy, int parentId, Guid? parentKey, EventMessages messages)
|
||||
: base(original, copy, parentId, parentKey, messages)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// A notification that is used to trigger the IElementService when the Delete and EmptyRecycleBin methods are called in the API.
|
||||
/// </summary>
|
||||
public sealed class ElementDeletedNotification : DeletedNotification<IElement>
|
||||
{
|
||||
public ElementDeletedNotification(IElement target, EventMessages messages)
|
||||
: base(target, messages)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// A notification that is used to trigger the IElementService when the DeleteVersion and DeleteVersions methods are called in the API, and the version has been deleted.
|
||||
/// </summary>
|
||||
public sealed class ElementDeletedVersionsNotification : DeletedVersionsNotification<IElement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ElementDeletedVersionsNotification"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">
|
||||
/// Gets the ID of the <see cref="IElement"/> object being deleted.
|
||||
/// </param>
|
||||
/// <param name="messages">
|
||||
/// Initializes a new instance of the <see cref="EventMessages"/>.
|
||||
/// </param>
|
||||
/// <param name="specificVersion">
|
||||
/// Gets the id of the IElement object version being deleted.
|
||||
/// </param>
|
||||
/// <param name="deletePriorVersions">
|
||||
/// False by default.
|
||||
/// </param>
|
||||
/// <param name="dateToRetain">
|
||||
/// Gets the latest version date.
|
||||
/// </param>
|
||||
public ElementDeletedVersionsNotification(
|
||||
int id,
|
||||
EventMessages messages,
|
||||
int specificVersion = default,
|
||||
bool deletePriorVersions = false,
|
||||
DateTime dateToRetain = default)
|
||||
: base(id, messages, specificVersion, deletePriorVersions, dateToRetain)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// A notification that is used to trigger the IElementService when the DeleteElementOfType, Delete and EmptyRecycleBin methods are called in the API.
|
||||
/// </summary>
|
||||
public sealed class ElementDeletingNotification : DeletingNotification<IElement>
|
||||
{
|
||||
public ElementDeletingNotification(IElement target, EventMessages messages)
|
||||
: base(target, messages)
|
||||
{
|
||||
}
|
||||
|
||||
public ElementDeletingNotification(IEnumerable<IElement> target, EventMessages messages)
|
||||
: base(target, messages)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// A notification that is used to trigger the IElementService when the DeleteVersion and DeleteVersions methods are called in the API.
|
||||
/// </summary>
|
||||
public sealed class ElementDeletingVersionsNotification : DeletingVersionsNotification<IElement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ElementDeletingVersionsNotification"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">
|
||||
/// Gets the ID of the <see cref="IElement"/> object being deleted.
|
||||
/// </param>
|
||||
/// <param name="messages">
|
||||
/// Initializes a new instance of the <see cref="EventMessages"/>.
|
||||
/// </param>
|
||||
/// <param name="specificVersion">
|
||||
/// Gets the id of the IElement object version being deleted.
|
||||
/// </param>
|
||||
/// <param name="deletePriorVersions">
|
||||
/// False by default.
|
||||
/// </param>
|
||||
/// <param name="dateToRetain">
|
||||
/// Gets the latest version date.
|
||||
/// </param>
|
||||
public ElementDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default)
|
||||
: base(id, messages, specificVersion, deletePriorVersions, dateToRetain)
|
||||
{
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user