using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models;
///
/// Provides extension methods for path validation.
///
internal static class PathValidationExtensions
{
///
/// Does a quick check on the entity's set path to ensure that it's valid and consistent
///
///
///
public static void ValidatePathWithException(this NodeDto entity)
{
// don't validate if it's empty and it has no id
if (entity.NodeId == default && entity.Path.IsNullOrWhiteSpace())
{
return;
}
if (entity.Path.IsNullOrWhiteSpace())
{
throw new InvalidDataException(
$"The content item {entity.NodeId} has an empty path: {entity.Path} with parentID: {entity.ParentId}");
}
var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
if (pathParts.Length < 2)
{
// a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id
throw new InvalidDataException(
$"The content item {entity.NodeId} has an invalid path: {entity.Path} with parentID: {entity.ParentId}");
}
if (entity.ParentId != default && pathParts[^2] != entity.ParentId.ToInvariantString())
{
// the 2nd last id in the path must be it's parent id
throw new InvalidDataException(
$"The content item {entity.NodeId} has an invalid path: {entity.Path} with parentID: {entity.ParentId}");
}
}
///
/// Does a quick check on the entity's set path to ensure that it's valid and consistent
///
///
///
public static bool ValidatePath(this IUmbracoEntity entity)
{
// don't validate if it's empty and it has no id
if (entity.HasIdentity == false && entity.Path.IsNullOrWhiteSpace())
{
return true;
}
if (entity.Path.IsNullOrWhiteSpace())
{
return false;
}
var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
if (pathParts.Length < 2)
{
// a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id
return false;
}
if (entity.ParentId != default && pathParts[^2] != entity.ParentId.ToInvariantString())
{
// the 2nd last id in the path must be it's parent id
return false;
}
return true;
}
///
/// This will validate the entity's path and if it's invalid it will fix it, if fixing is required it will recursively
/// check and fix all ancestors if required.
///
///
///
/// A callback specified to retrieve the parent entity of the entity
/// A callback specified to update a fixed entity
public static void EnsureValidPath(
this T entity,
ILogger logger,
Func getParent,
Action update)
where T : IUmbracoEntity
{
if (entity.HasIdentity == false)
{
throw new InvalidOperationException(
"Could not ensure the entity path, the entity has not been assigned an identity");
}
if (entity.ValidatePath() == false)
{
logger.LogWarning(
"The content item {EntityId} has an invalid path: {EntityPath} with parentID: {EntityParentId}",
entity.Id, entity.Path, entity.ParentId);
if (entity.ParentId == -1)
{
entity.Path = string.Concat("-1,", entity.Id);
// path changed, update it
update(entity);
}
else
{
T? parent = getParent(entity);
if (parent == null)
{
throw new NullReferenceException("Could not ensure path for entity " + entity.Id +
" could not resolve it's parent " + entity.ParentId);
}
// the parent must also be valid!
parent.EnsureValidPath(logger, getParent, update);
entity.Path = string.Concat(parent.Path, ",", entity.Id);
// path changed, update it
update(entity);
}
}
}
}