Created
February 2, 2024 15:46
-
-
Save barteksekula/78ff1df20df2f2449497d94c218cfdad to your computer and use it in GitHub Desktop.
validate if IContent does not have unpublished depencies
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Globalization; | |
using System.Web; | |
using EPiServer.Approvals; | |
using EPiServer.Approvals.ContentApprovals; | |
using EPiServer.Cms.Shell; | |
using EPiServer.Cms.Shell.Service.Internal; | |
using EPiServer.Cms.Shell.UI.Rest.Models; | |
using EPiServer.Cms.Shell.UI.Rest.Models.Internal; | |
using EPiServer.Core; | |
using EPiServer.Core.Internal; | |
using EPiServer.DataAbstraction; | |
using EPiServer.Framework; | |
using EPiServer.Framework.Initialization; | |
using EPiServer.Globalization; | |
using EPiServer.ServiceLocation; | |
using EPiServer.Shell; | |
using SiteDefinition = EPiServer.Web.SiteDefinition; | |
namespace EPiServer.Cms.UI.VisualBuilder; | |
[InitializableModule] | |
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))] | |
public class PublishEventInitializationModule : IInitializableModule | |
{ | |
private DependenciesResolver dependenciesResolver; | |
public void Initialize(InitializationEngine context) | |
{ | |
//Add initialization logic, this method is called once after CMS has been initialized | |
var contentEvents = ServiceLocator.Current.GetInstance<IContentEvents>(); | |
contentEvents.PublishingContent += contentEvents_PublishingContent; | |
dependenciesResolver = ServiceLocator.Current.GetInstance<DependenciesResolver>(); | |
} | |
void contentEvents_PublishingContent(object sender, EPiServer.ContentEventArgs e) | |
{ | |
var dependencies = dependenciesResolver.GetUnpublishedDependencies(e.ContentLink).ToList(); | |
if (!dependencies.Any()) return; | |
var text = dependencies.Count == 1 ? "dependency" : "dependencies"; | |
e.CancelAction = true; | |
e.CancelReason = | |
$"You can't publish because you have {dependencies.Count} unpublished {text}. {string.Join(',', dependencies.Select(x => $"{x.Name} [{x.ContentLink}] "))}"; | |
} | |
public void Preload(string[] parameters) | |
{ | |
} | |
public void Uninitialize(InitializationEngine context) | |
{ | |
//Add uninitialization logic | |
var contentEvents = ServiceLocator.Current.GetInstance<IContentEvents>(); | |
contentEvents.PublishingContent -= contentEvents_PublishingContent; | |
} | |
} | |
public static class ContentExtensions | |
{ | |
public static ExtendedVersionStatus GetCalculatedStatus(this IContent content) | |
{ | |
var versionableContent = content as IVersionable; | |
if (versionableContent == null) | |
{ | |
return ExtendedVersionStatus.Published; | |
} | |
if (versionableContent.HasExpired()) | |
{ | |
return ExtendedVersionStatus.Expired; | |
} | |
int status = (int)versionableContent.Status; | |
return (ExtendedVersionStatus)status; | |
} | |
public static bool HasExpired(this IVersionable content) | |
{ | |
if (content.Status == VersionStatus.Published && content.StopPublish < DateTime.Now) | |
{ | |
return true; | |
} | |
return false; | |
} | |
} | |
[ServiceConfiguration(typeof(ApprovalResolver))] | |
public class ApprovalResolver(IApprovalDefinitionRepository approvalDefinitionRepository) | |
{ | |
public bool IsPartOfActiveApproval(IContent content) | |
{ | |
var definition = approvalDefinitionRepository.ResolveAsync(content.ContentLink).ConfigureAwait(false) | |
.GetAwaiter().GetResult(); | |
var isEnabled = definition != null && definition.Definition != null && definition.Definition.IsEnabled; | |
var extendedVersionStatus = content.GetCalculatedStatus(); | |
var isReadyToPublish = extendedVersionStatus == ExtendedVersionStatus.CheckedIn; | |
return isEnabled && !isReadyToPublish; | |
} | |
} | |
[ServiceConfiguration(typeof(DependenciesResolver))] | |
public class DependenciesResolver( | |
ContentSoftLinkIndexer contentSoftLinkIndexer, | |
IContentRepository contentRepository, | |
LanguageResolver languageResolver, | |
ContentLoaderService contentLoaderService, | |
ServiceAccessor<SiteDefinition> currentSiteDefinition, | |
UIDescriptorRegistry uiDescriptorRegistry, | |
ApprovalResolver approvalResolver) | |
{ | |
private static bool IsCorrectStatus(ExtendedVersionStatus versionStatus) | |
{ | |
return versionStatus is ExtendedVersionStatus.CheckedOut or ExtendedVersionStatus.CheckedIn | |
or ExtendedVersionStatus.Rejected or ExtendedVersionStatus.PreviouslyPublished; | |
} | |
private ContentReference GetUnpublishedVersion(IContent innerContent, CultureInfo preferredCulture) | |
{ | |
if (IsCorrectStatus(innerContent.GetCalculatedStatus())) | |
{ | |
return innerContent.ContentLink; | |
} | |
if (!(innerContent is IVersionable block)) | |
{ | |
return null; | |
} | |
if (block.Status != VersionStatus.Published) | |
{ | |
return null; | |
} | |
var contentVersion = contentLoaderService.GetCommonDraft(innerContent.ContentLink, preferredCulture); | |
if (contentVersion == null) | |
{ | |
return null; | |
} | |
var status = (ExtendedVersionStatus)(int)contentVersion.Status; | |
if (IsCorrectStatus(status)) | |
{ | |
return contentVersion.ContentLink; | |
} | |
var item = contentRepository.Get<IContent>(contentVersion.ContentLink); | |
if (approvalResolver.IsPartOfActiveApproval(item)) | |
{ | |
return contentVersion.ContentLink; | |
} | |
return null; | |
} | |
private IEnumerable<ContentReference> GetDependencies(IContent content) | |
{ | |
return contentSoftLinkIndexer.GetLinks(content) | |
.Where(x => x.SoftLinkType == ReferenceType.PageLinkReference && | |
!(x.ReferencedContentLink is PageReference)) | |
.Select(x => x.ReferencedContentLink.ToReferenceWithoutVersion()) | |
.Where(x => x != ContentReference.EmptyReference); | |
} | |
private IEnumerable<ContentReference> GetLanguageAgnosticDependencies(IContent content) | |
{ | |
var currentLanguageDependencies = GetDependencies(content); | |
if (content.IsMasterLanguageBranch()) | |
{ | |
return currentLanguageDependencies; | |
} | |
var masterLanguage = ((ILocalizable)content).MasterLanguage; | |
var masterVersion = | |
contentRepository.Get<IContent>(content.ContentLink.ToReferenceWithoutVersion(), masterLanguage); | |
var masterVersionDependencies = GetDependencies(masterVersion); | |
return currentLanguageDependencies.Union(masterVersionDependencies); | |
} | |
public IEnumerable<Dependency> GetUnpublishedDependencies(ContentReference contentLink) | |
{ | |
var dependencies = new List<Dependency>(); | |
var root = contentRepository.Get<IContent>(contentLink); | |
var directDependencies = GetLanguageAgnosticDependencies(root).ToList(); | |
foreach (var directDependency in directDependencies) | |
{ | |
var content = contentRepository.Get<IContent>(directDependency); | |
if (!(content is BlockData)) continue; | |
var reference = GetUnpublishedVersion(content, languageResolver.GetPreferredCulture()); | |
var referenceContent = reference != null ? contentRepository.Get<IContent>(reference) : content; | |
var dependency = new Dependency | |
{ | |
CanBePublished = reference != null, | |
ContentLink = reference ?? content.ContentLink, | |
Name = referenceContent.Name, | |
Uri = referenceContent.GetUri(), | |
#pragma warning disable CS0618 // Type or member is obsolete | |
EditableUrl = referenceContent.EditablePreviewUrl() | |
#pragma warning restore CS0618 // Type or member is obsolete | |
}; | |
var subDependencies = GetLanguageAgnosticDependencies(referenceContent).ToList(); | |
foreach (var subDependency in subDependencies) | |
{ | |
var subContent = contentRepository.Get<IContent>(subDependency); | |
var subReference = GetUnpublishedVersion(subContent, languageResolver.GetPreferredCulture()); | |
if (subReference == null) continue; | |
if (dependencies.SelectMany(d => d.References.Select(x => x.ContentLink)).Contains(subReference)) | |
{ | |
continue; | |
} | |
var subContentVersion = contentRepository.Get<IContent>(subReference); | |
var richContentReferenceModel = new ContentReferenceModel() | |
{ | |
ContentLink = subReference, | |
Language = subContentVersion.LanguageBranch(), | |
Name = subContentVersion.Name, | |
TreePath = GetTreePath(subContentVersion), | |
Uri = subContent.GetUri(), | |
TypeIdentifier = GetTypeIdentifier(subContentVersion), | |
}; | |
dependency.References.Add(richContentReferenceModel); | |
} | |
if (dependency.CanBePublished || dependency.References.Count > 0) | |
{ | |
dependencies.Add(dependency); | |
} | |
} | |
return dependencies; | |
} | |
private string GetTypeIdentifier(IContent c) | |
{ | |
return uiDescriptorRegistry.GetTypeIdentifiers(c.GetType()).FirstOrDefault(); | |
} | |
private IEnumerable<string> GetTreePath(IContent content) | |
{ | |
var siteDefinition = currentSiteDefinition(); | |
return contentLoaderService.GetAncestorNames(content, siteDefinition) | |
.Select(HttpUtility.HtmlEncode); | |
} | |
} | |
public class Dependency | |
{ | |
public string Name { get; set; } | |
public bool CanBePublished { get; set; } | |
public ContentReference ContentLink { get; set; } | |
public Uri Uri { get; set; } | |
public string EditableUrl { get; set; } | |
public List<ContentReferenceModel> References { get; set; } = []; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment