Skip to content

Instantly share code, notes, and snippets.

@gregwiechec
Created December 6, 2023 12:16
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 gregwiechec/a63abc58b2419be26c6d3baabc1671e8 to your computer and use it in GitHub Desktop.
Save gregwiechec/a63abc58b2419be26c6d3baabc1671e8 to your computer and use it in GitHub Desktop.
Update referenced content
using System.Collections.Generic;
using System.Linq;
using EPiServer;
using EPiServer.Cms.Shell.UI.Rest.Internal;
using EPiServer.Core;
using EPiServer.Core.Html.StringParsing;
using EPiServer.Data.Entity;
using EPiServer.DataAccess;
using EPiServer.Security;
using EPiServer.Shell.Services.Rest;
using EPiServer.SpecializedProperties;
using EPiServer.Web;
using Microsoft.AspNetCore.Mvc;
namespace AlloyTemplates.Business;
[RestStore("content-reference-update")]
public class ContentReferenceUpdateStore : RestControllerBase
{
private readonly ReferencedContentResolver _referencedContentResolver;
private readonly IContentRepository _contentRepository;
public ContentReferenceUpdateStore(
ReferencedContentResolver referencedContentResolver,
IContentRepository contentRepository)
{
_referencedContentResolver = referencedContentResolver;
_contentRepository = contentRepository;
}
[HttpPost]
public ActionResult Post([FromBody] UpdateReferenceModel model)
{
if (model == null)
{
return BadRequest();
}
var references = _referencedContentResolver.GetReferenceList(new ContentReference(model.SourceReference));
if (!string.IsNullOrWhiteSpace(model.SourceContentLinkToReplace))
{
var sourceContentReference = new ContentReference(model.SourceContentLinkToReplace);
references = references.Where(x => x.ContentLink == sourceContentReference).ToList();
}
foreach (var reference in references)
{
var updated = false;
var content = _contentRepository.Get<IContent>(reference.ContentLink);
if (content is IReadOnly)
{
content = (content as IReadOnly).CreateWritableClone() as IContent;
}
for (var index = 0; index < content.Property.Count; index++)
{
var propertyData = content.Property[index];
if (TryUpdateProperty(propertyData, new ContentReference(model.SourceReference),
new ContentReference(model.TargetReference)))
{
updated = true;
}
}
if (updated)
{
_contentRepository.Save(content, SaveAction.ForceCurrentVersion, AccessLevel.NoAccess);
}
}
return Ok();
}
private bool TryUpdateProperty(PropertyData propertyData, ContentReference fromReference, ContentReference toReference)
{
if (propertyData.Value == null)
{
return false;
}
//
// ContentArea
//
if (propertyData.Value is ContentArea)
{
var contentArea = propertyData.Value as ContentArea;
if (contentArea.Items == null)
{
return false;
}
if (contentArea.Items.Any(x => x.ContentLink == fromReference.ToReferenceWithoutVersion()))
{
for (var index = 0; index < contentArea.Items.Count; index++)
{
var contentAreaItem = contentArea.Items[index];
if (contentAreaItem.ContentLink == fromReference)
{
contentAreaItem.ContentLink = toReference;
}
}
return true;
}
}
//
// ContentReference
//
if (propertyData.Value is ContentReference)
{
if (((ContentReference)propertyData.Value) == fromReference)
{
propertyData.Value = toReference;
return true;
}
}
//
// ContentReference list
//
if (propertyData.Value is IEnumerable<ContentReference>)
{
var references = (propertyData.Value as IEnumerable<ContentReference>).ToList();
if (references.Any(x => x == fromReference))
{
propertyData.Value = references.Select(x => (x == fromReference) ? toReference : x).ToList();
return true;
}
}
if (propertyData.Value is LinkItemCollection ||
propertyData.Value is XhtmlString ||
propertyData.Value is LinkItem)
{
var hasLink = false;
var content = _contentRepository.Get<IContent>(fromReference);
var currentVirtualPath =
PermanentLinkUtility.GetPermanentLinkVirtualPath(content.ContentGuid, ".aspx");
var newContent = _contentRepository.Get<IContent>(toReference);
var newVirtualPath = PermanentLinkUtility.GetPermanentLinkVirtualPath(newContent.ContentGuid, ".aspx");
//
// LinkItemCollection
//
if (propertyData.Value is LinkItemCollection)
{
var updatedList = new List<LinkItem>();
var linkItems = (LinkItemCollection)propertyData.Value;
for (var index = 0; index < linkItems.Count; index++)
{
var linkItem = linkItems[index];
if (linkItem.Href == currentVirtualPath)
{
linkItem.Href = newVirtualPath;
linkItem.Text = newContent.Name;
hasLink = true;
}
updatedList.Add(linkItem);
}
if (hasLink)
{
propertyData.Value = new LinkItemCollection(updatedList);
return true;
}
return false;
}
//
// LinkItem
//
if (propertyData.Value is LinkItem)
{
var linkItem = (LinkItem)propertyData.Value;
if (linkItem.Href == currentVirtualPath)
{
var linkItemCopy = new LinkItem
{
Href = newVirtualPath,
Text = newContent.Name,
Target = linkItem.Target,
Title = linkItem.Title
};
linkItemCopy.Attributes.Clear();
foreach (var attr in linkItem.Attributes)
{
linkItemCopy.Attributes.Add(attr.Key, attr.Value);
}
linkItemCopy.Attributes["href"] = newVirtualPath;
propertyData.Value = linkItemCopy;
return true;
}
return false;
}
//
// XhtmlString
//
if (propertyData.Value is XhtmlString)
{
var xhtml = (XhtmlString)propertyData.Value;
var str = xhtml.ToHtmlString();
foreach (var stringFragment in xhtml.Fragments)
{
if (stringFragment is UrlFragment)
{
var urlFragment = (UrlFragment)stringFragment;
if (urlFragment.Url == currentVirtualPath)
{
str = str.Replace(urlFragment.Url.Replace("~", ""), newVirtualPath.Replace("~", ""));
hasLink = true;
}
}
}
if (hasLink)
{
propertyData.Value = new XhtmlString(str);
return true;
}
return false;
}
}
return false;
}
}
public class UpdateReferenceModel
{
public string SourceReference { get; set; }
public string SourceContentLinkToReplace { get; set; }
public string TargetReference { get; set; }
}
define([// Dojo
"dojo",
"dojo/_base/declare",
"dojo/on",
"dijit/registry",
//CMS
"epi/_Module",
"epi/dependency",
"epi/routes",
"epi/shell/store/JsonRest",
"epi/shell/widget/dialog/Dialog",
"epi-cms/widget/ContentReferences",
"epi-cms/widget/ContentSelector",
"epi/i18n!epi/cms/nls/episerver.shared.action"
],
function (
// Dojo
dojo,
declare,
on,
dijitRegistry,
//CMS
_Module,
dependency,
routes,
JsonRest,
Dialog,
ContentReferences,
ContentSelector,
resources
) {
// last selected link cleared when opening references dialog
var lastSelectedLink = null;
// create dialog that allows to replace links
function createDialog(container) {
var contentSelector = new ContentSelector({
roots: [1]
});
var dialog = new Dialog({
dialogClass: "epi-dialog--wide",
defaultActionsVisible: true,
confirmActionText: "Change",
content: contentSelector,
title: "Replace content with another content",
focusActionsOnLoad: true
});
container.own(dialog);
dialog.own(contentSelector);
on.once(dialog, "show", function (value) {
// set epiStringList class to have width 100% for property
contentSelector.displayNode.classList.add("epiStringList");
if (lastSelectedLink) {
contentSelector.set("value", lastSelectedLink);
}
});
var resolved = false;
return new Promise((resolve) => {
on.once(dialog, "execute", function () {
resolved = true;
resolve(contentSelector.get("value"));
});
on.once(dialog, "hide", function () {
if (!resolved) {
resolve("");
}
});
dialog.show();
});
}
function replaceReferences(selectedContentLink, container, sourceContentLink) {
createDialog(container).then((contentLink) => {
if (!contentLink) {
return;
}
lastSelectedLink = contentLink;
var updateParameters = {
sourceReference: sourceContentLink,
targetReference: contentLink
};
// when empty then run Replace All
if (selectedContentLink) {
updateParameters.sourceContentLinkToReplace = selectedContentLink;
}
var registry = dependency.resolve("epi.storeregistry");
var store = registry.get("app.updatereferences");
store.add(updateParameters).then(function () {
container.fetchData();
});
});
}
function patchStartup() {
var originalStartup = ContentReferences.prototype.startup;
ContentReferences.prototype.startup = function () {
lastSelectedLink = null;
var result = originalStartup.apply(this, arguments);
var self = this;
this.grid.on(".dgrid-column-uri a.link-replace:click", function(e) {
e.preventDefault();
e.stopPropagation();
var selectedContentLink = Object.keys(self.grid.selection)[0];
var sourceContentLink = self.model.contentItems[0].contentLink;
replaceReferences(selectedContentLink, self, sourceContentLink);
});
this.grid.columns.uri.className = "epi-width20"
return result;
}
ContentReferences.prototype.startup.nom = "startup";
}
// do not change context when click on Replace link
function patchChangeContext() {
var originalChangeContext = ContentReferences.prototype._onChangeContext;
ContentReferences.prototype._onChangeContext = function (e) {
if (e.target.classList.contains("link-replace")) {
return;
}
return originalChangeContext.apply(this, arguments);
}
ContentReferences.prototype._onChangeContext.nom = "_onChangeContext";
}
// add Replace link to grid
function patchGetLinkTemplate() {
var orignalGetTemplateLink = ContentReferences.prototype._getLinkTemplate;
ContentReferences.prototype._getLinkTemplate = function () {
var result = orignalGetTemplateLink.apply(this, arguments);
var replaceLink = document.createElement("a");
replaceLink.classList.add("epi-visibleLink");
replaceLink.classList.add("link-replace");
replaceLink.innerHTML = resources.replace;
replaceLink.title = "Replace usage with another link";
replaceLink.style.marginLeft = "8px";
return "<div>" + result + replaceLink.outerHTML + "</div>";
}
ContentReferences.prototype._getLinkTemplate.nom = "_getLinkTemplate";
}
// add additional dialog button that allows to replace all references
function patchDialog() {
var origianlGetActions = Dialog.prototype.getActions;
Dialog.prototype.getActions = function () {
var self = this;
var result = origianlGetActions.apply(this, arguments);
if (this.dialogClass === "epi-dialog-contentReferences") {
var replaceButton = {
name: "Replace All",
label: "Replace All",
title: null,
action: function() {
// find grid widget
var contentReferencesWidget = dijitRegistry.getEnclosingWidget(self.containerNode).getChildren()[0];
var selectedContentLink = contentReferencesWidget.contentItems[0].contentLink;
replaceReferences(null, contentReferencesWidget, selectedContentLink);
}
}
return [result[0], replaceButton, result[1]];
}
return result;
}
Dialog.prototype.getActions.nom = "getActions";
}
// initialize store for updating references
function initializeStore() {
var registry = dependency.resolve("epi.storeregistry");
registry.add("app.updatereferences",
new JsonRest({
target: routes.getRestPath({ moduleArea: "app", storeName: "content-reference-update" })
})
);
}
return declare([_Module], {
// summary: Module initializer for the default module.
initialize: function () {
this.inherited(arguments);
initializeStore();
patchStartup();
patchChangeContext();
patchGetLinkTemplate();
patchDialog();
}
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment