Skip to content

Instantly share code, notes, and snippets.

@barteksekula
Created February 2, 2024 15:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barteksekula/78ff1df20df2f2449497d94c218cfdad to your computer and use it in GitHub Desktop.
Save barteksekula/78ff1df20df2f2449497d94c218cfdad to your computer and use it in GitHub Desktop.
validate if IContent does not have unpublished depencies
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