Relations: Exclude the relate parent on delete relation type from checks for related documents and media on delete, when disable delete with references is enabled (closes #20803) (#20811)
* Exclude the relate parent on delete relation type from checks for related documents and media on delete, when disable delete with references is enabled. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Applied suggestions from code review. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -30,6 +30,14 @@ public interface IQuery<T>
|
||||
/// <returns>This instance so calls to this method are chainable</returns>
|
||||
IQuery<T> WhereIn(Expression<Func<T, object>> fieldSelector, IEnumerable? values);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a where-not-in clause to the query
|
||||
/// </summary>
|
||||
/// <param name="fieldSelector"></param>
|
||||
/// <param name="values"></param>
|
||||
/// <returns>This instance so calls to this method are chainable</returns>
|
||||
IQuery<T> WhereNotIn(Expression<Func<T, object>> fieldSelector, IEnumerable? values) => throw new NotImplementedException(); // TODO (V18): Remove default implementation.
|
||||
|
||||
/// <summary>
|
||||
/// Adds a set of OR-ed where clauses to the query.
|
||||
/// </summary>
|
||||
|
||||
@@ -62,6 +62,9 @@ internal sealed class ContentEditingService
|
||||
_languageService = languageService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string? RelateParentOnDeleteAlias => Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias;
|
||||
|
||||
public Task<IContent?> GetAsync(Guid key)
|
||||
{
|
||||
IContent? content = ContentService.GetById(key);
|
||||
|
||||
@@ -75,6 +75,11 @@ internal abstract class ContentEditingServiceBase<TContent, TContentType, TConte
|
||||
|
||||
protected TContentTypeService ContentTypeService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alias used to relate the parent entity when handling content (document or media) delete operations.
|
||||
/// </summary>
|
||||
protected virtual string? RelateParentOnDeleteAlias => null;
|
||||
|
||||
protected async Task<Attempt<TContentCreateResult, ContentEditingOperationStatus>> MapCreate<TContentCreateResult>(ContentCreationModelBase contentCreationModelBase)
|
||||
where TContentCreateResult : ContentCreateResultBase<TContent>, new()
|
||||
{
|
||||
@@ -202,9 +207,27 @@ internal abstract class ContentEditingServiceBase<TContent, TContentType, TConte
|
||||
return Attempt.FailWithStatus<TContent?, ContentEditingOperationStatus>(status, content);
|
||||
}
|
||||
|
||||
if (disabledWhenReferenced && _relationService.IsRelated(content.Id, RelationDirectionFilter.Child))
|
||||
if (disabledWhenReferenced)
|
||||
{
|
||||
return Attempt.FailWithStatus<TContent?, ContentEditingOperationStatus>(referenceFailStatus, content);
|
||||
// When checking if an item is related, we may need to exclude the "relate parent on delete" relation type, as this prevents
|
||||
// deleting from the recycle bin.
|
||||
int[]? excludeRelationTypeIds = null;
|
||||
if (string.IsNullOrWhiteSpace(RelateParentOnDeleteAlias) is false)
|
||||
{
|
||||
IRelationType? relateParentOnDeleteRelationType = _relationService.GetRelationTypeByAlias(RelateParentOnDeleteAlias);
|
||||
if (relateParentOnDeleteRelationType is not null)
|
||||
{
|
||||
excludeRelationTypeIds = [relateParentOnDeleteRelationType.Id];
|
||||
}
|
||||
}
|
||||
|
||||
if (_relationService.IsRelated(
|
||||
content.Id,
|
||||
RelationDirectionFilter.Child,
|
||||
excludeRelationTypeIds: excludeRelationTypeIds))
|
||||
{
|
||||
return Attempt.FailWithStatus<TContent?, ContentEditingOperationStatus>(referenceFailStatus, content);
|
||||
}
|
||||
}
|
||||
|
||||
var userId = await GetUserIdAsync(userKey);
|
||||
|
||||
@@ -297,9 +297,24 @@ public interface IRelationService : IService
|
||||
/// </summary>
|
||||
/// <param name="id">Id of an object to check relations for</param>
|
||||
/// <param name="directionFilter">Indicates whether to check for relations as parent, child or in either direction.</param>
|
||||
/// <returns>Returns <c>True</c> if any relations exists with the given Id, otherwise <c>False</c></returns>
|
||||
/// <returns>Returns <c>True</c> if any relations exists with the given Id, otherwise <c>False</c>.</returns>
|
||||
[Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
bool IsRelated(int id, RelationDirectionFilter directionFilter);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether any relations exists for the passed in Id and direction.
|
||||
/// </summary>
|
||||
/// <param name="id">Id of an object to check relations for</param>
|
||||
/// <param name="directionFilter">Indicates whether to check for relations as parent, child or in either direction.</param>
|
||||
/// <param name="includeRelationTypeIds">A collection of relation type Ids to include consideration in the relation checks.</param>
|
||||
/// <param name="excludeRelationTypeIds">A collection of relation type Ids to exclude from consideration in the relation checks.</param>
|
||||
/// <remarks>If no relation type Ids are provided in includeRelationTypeIds or excludeRelationTypeIds, all relation type Ids are considered.</remarks>
|
||||
/// <returns>Returns <c>True</c> if any relations exists with the given Id, otherwise <c>False</c>.</returns>
|
||||
bool IsRelated(int id, RelationDirectionFilter directionFilter, int[]? includeRelationTypeIds = null, int[]? excludeRelationTypeIds = null)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> IsRelated(id, directionFilter);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether two items are related
|
||||
/// </summary>
|
||||
|
||||
@@ -43,6 +43,9 @@ internal sealed class MediaEditingService
|
||||
contentTypeFilters)
|
||||
=> _logger = logger;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string? RelateParentOnDeleteAlias => Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias;
|
||||
|
||||
public Task<IMedia?> GetAsync(Guid key)
|
||||
{
|
||||
IMedia? media = ContentService.GetById(key);
|
||||
|
||||
@@ -485,11 +485,14 @@ public class RelationService : RepositoryService, IRelationService
|
||||
return _relationRepository.Get(query).Any();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsRelated(int id) => IsRelated(id, RelationDirectionFilter.Any);
|
||||
[Obsolete("No longer used in Umbraco, please the overload taking all parameters. Scheduled for removal in Umbraco 19.")]
|
||||
public bool IsRelated(int id) => IsRelated(id, RelationDirectionFilter.Any, null, null);
|
||||
|
||||
[Obsolete("Please the overload taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
public bool IsRelated(int id, RelationDirectionFilter directionFilter) => IsRelated(id, directionFilter, null, null);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsRelated(int id, RelationDirectionFilter directionFilter)
|
||||
public bool IsRelated(int id, RelationDirectionFilter directionFilter, int[]? includeRelationTypeIds = null, int[]? excludeRelationTypeIds = null)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
|
||||
IQuery<IRelation> query = Query<IRelation>();
|
||||
@@ -502,6 +505,16 @@ public class RelationService : RepositoryService, IRelationService
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(directionFilter)),
|
||||
};
|
||||
|
||||
if (includeRelationTypeIds is not null && includeRelationTypeIds.Length > 0)
|
||||
{
|
||||
query = query.WhereIn(x => x.RelationTypeId, includeRelationTypeIds);
|
||||
}
|
||||
|
||||
if (excludeRelationTypeIds is not null && excludeRelationTypeIds.Length > 0)
|
||||
{
|
||||
query = query.WhereNotIn(x => x.RelationTypeId, excludeRelationTypeIds);
|
||||
}
|
||||
|
||||
return _relationRepository.Get(query).Any();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user