using System; using System.IO; 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(int) && 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(int) && pathParts[pathParts.Length - 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(int) && pathParts[pathParts.Length - 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 { var 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); } } } } }