Skip to content

Instantly share code, notes, and snippets.

@j3rbr0wn
Created June 12, 2023 12:14
Show Gist options
  • Select an option

  • Save j3rbr0wn/29a197364b60c57d619b8070016566d2 to your computer and use it in GitHub Desktop.

Select an option

Save j3rbr0wn/29a197364b60c57d619b8070016566d2 to your computer and use it in GitHub Desktop.
Content Cleaner Tool for Optimizely CMS 12. Admin plugin to allow finding content of a specific type and deleting instances.
@using EPiServer.Framework.Localization
@using EPiServer.Web.Mvc.Html
@using EPiServer.Shell.Web.Mvc.Html
@using EPiServer.Core
@using EPiServer.Web
@using EPiServer.Web.Mvc
@using EPiServer.Web.Routing
@using MyCms12Website.Models.ViewModels
@using MyCms12Website.Models.Blocks
@using MyCms12Website.Models.Pages
@using Microsoft.AspNetCore.Mvc.Razor
@using Microsoft.AspNetCore.Html
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
using EPiServer.Authorization;
using EPiServer.Shell.Navigation;
namespace MyCms12Website.Business.Plugins;
[MenuProvider]
public class AdminToolMenuProvider : IMenuProvider
{
public IEnumerable<MenuItem> GetMenuItems()
{
var contentCleaner = new UrlMenuItem("Content Cleaner", MenuPaths.Global + "/cms/admin/contentcleanertool", "/contentcleanertool/index")
{
IsAvailable = _ => true,
SortIndex = 70,
AuthorizationPolicy = CmsPolicyNames.CmsAdmin
};
return new List<MenuItem>(1)
{
contentCleaner
};
}
}
@using EPiServer.Framework.Web.Resources
@using EPiServer.Shell.Navigation
@using EPiServer.Editor
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model MyCms12Website.Business.Plugins.ContentCleanerTool.ContentCleanerToolViewModel
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Content Type Cleaner Tool</title>
<!-- Shell -->
@ClientResources.RenderResources("ShellCore")
<!-- LightTheme -->
@ClientResources.RenderResources("ShellCoreLightTheme")
</head>
<body class="epi-workspace">
@Html.AntiForgeryToken()
@Html.Raw(Html.CreatePlatformNavigationMenu())
<div @Html.Raw(Html.ApplyPlatformNavigation())>
<div class="epi-contentContainer epi-padding">
<div class="epi-contentArea">
<h1 class="EP-prefix">Content Type Cleaner Tool</h1>
<div>
<p id="message"></p>
</div>
<div>
<form action="/contentcleanertool/index/" method="post" id="contentTypeSelectionForm">
<h2>Content Type Selection</h2>
<div>
<label asp-for="ContentTypeId">Select content type:</label>
<select asp-for="ContentTypeId" asp-items="@Model.ContentItems">
</select>
<button type="submit" class="epi-cmsButton">List content</button>
</div>
</form>
</div>
@if (Model.ContentUsages?.Any() ?? false)
{
<div>
<h2>Content</h2>
<table>
<tr>
<th>Id</th>
<th>Location</th>
<th></th>
<th></th>
</tr>
@foreach (var item in Model.ContentUsages)
{
<tr>
<td>
@item.Content.ContentLink.ID
</td>
<td>
<a href="@item.ContentUrl" target="_blank">
@item.Breadcrumb
</a>
</td>
<td>
<a href="@PageEditing.GetEditUrl(item.Content.ContentLink)" target="_blank">
Edit
</a>
</td>
<td>
<button class="js-delete-button" data-contentref="@item.Content.ContentLink">
Delete
</button>
</td>
</tr>
}
</table>
</div>
}
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js" crossorigin="anonymous"></script>
<script>
$(document).ready(function () {
$('.js-delete-button').click(function () {
let contentRef = $(this).data('contentref');
$.get('/contentcleanertool/delete/', { contentRef: contentRef })
.done(function (data) {
$( "#contentTypeSelectionForm" ).trigger( "submit" );
})
.fail(function(jqXHR, textStatus, errorThrown) {
$( "#message" ).text( `Delete failed - ${jqXHR.responseText}` );
});
});
});
</script>
</body>
</html>
using EPiServer.Security;
using EPiServer.Web.Routing;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Net;
using System.Text;
namespace MyCms12Website.Business.Plugins.ContentCleanerTool.Controllers;
[Authorize(Roles = "Administrators")]
[Route("[controller]")]
public class ContentCleanerToolController : Controller
{
private readonly IContentRepository _contentRepository;
private readonly IContentModelUsage _contentModelUsage;
private readonly IContentTypeRepository _contentTypeRepository;
private readonly IUrlResolver _urlResolver;
public ContentCleanerToolController(IUrlResolver urlResolver, IContentTypeRepository contentTypeRepository, IContentModelUsage contentModelUsage, IContentRepository contentRepository)
{
_urlResolver = urlResolver;
_contentTypeRepository = contentTypeRepository;
_contentModelUsage = contentModelUsage;
_contentRepository = contentRepository;
}
[Route("[action]")]
[HttpGet]
public IActionResult Index()
{
var model = new ContentCleanerToolViewModel()
{
ContentItems = GetContentTypes()
};
return View("/Business/Plugins/ContentCleanerTool/Views/ContentCleanerTool/Index.cshtml", model);
}
[Route("[action]")]
[HttpPost]
public IActionResult Index(ContentCleanerToolViewModel model)
{
model.ContentItems = GetContentTypes();
if (ModelState.IsValid)
{
model.ContentUsages = GetContentTypeUsages(model.ContentTypeId);
}
return View("/Business/Plugins/ContentCleanerTool/Views/ContentCleanerTool/Index.cshtml", model);
}
[Route("[action]")]
[HttpGet]
public IActionResult Delete(string contentRef)
{
try
{
if (ModelState.IsValid)
{
DeleteItem(contentRef);
}
return new OkResult();
}
catch (Exception ex)
{
return new ContentResult
{
StatusCode = (int)HttpStatusCode.InternalServerError,
ContentType = "application/json",
Content = $"Error - {ex.Message}"
};
}
}
private void DeleteItem(string contentRef)
{
_contentRepository.Delete(ContentReference.Parse(contentRef), forceDelete: false, access: AccessLevel.NoAccess);
}
private List<ContentUsageBreadcrumb<ContentUsage>> GetContentTypeUsages(int selectedTypeID)
{
var contentType = _contentTypeRepository.Load(selectedTypeID);
return _contentModelUsage.ListContentOfContentType(contentType)
.Select(x => new ContentUsageBreadcrumb<ContentUsage>(x)
{
Breadcrumb = BreadCrumb(x.ContentLink),
ContentUrl = _urlResolver.GetUrl(x.ContentLink)
})
.OrderBy(x => x.Name)
.ToList();
}
private IEnumerable<SelectListItem> GetContentTypes()
{
var uploads = _contentTypeRepository.List().OrderBy(p => p.Name);
return uploads.Select(x => new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Name
}).ToList();
}
public string BreadCrumb(ContentReference cReference)
{
if (cReference == null)
return string.Empty;
var ancestor = _contentRepository.GetAncestors(cReference).ToList();
var content = _contentRepository.Get<IContent>(cReference);
var path = new StringBuilder();
for (var x = ancestor.Count - 1; x >= 0; x--)
{
path.Append($"\\{ancestor[x].Name}");
}
return $"{path}\\{content.Name}".Trim("\\".ToCharArray());
}
}
using Microsoft.AspNetCore.Mvc.Rendering;
namespace MyCms12Website.Business.Plugins.ContentCleanerTool;
public class ContentCleanerToolViewModel
{
public IEnumerable<SelectListItem> ContentItems { get; set; }
public IEnumerable<ContentUsageBreadcrumb<ContentUsage>> ContentUsages { get; set; } = new List<ContentUsageBreadcrumb<ContentUsage>>();
public int ContentTypeId { get; set; }
}
public class ContentUsageBreadcrumb<TContent> : ContentUsage
{
public ContentUsageBreadcrumb(TContent content)
{
Content = content;
}
public TContent Content { get; }
public string ContentUrl { get; set; }
public string Breadcrumb { get; set; }
}
@PNergard
Copy link
Copy Markdown

Nice one getting this to CMS 12.

@Brian-Jaxon
Copy link
Copy Markdown

Nice Tool. Can you explain the need for the ModelState.IsValid check in the POST Method of the controller? For me it only returns content items if I comment out that check and I'm curious about it's intented purpose.

@j3rbr0wn
Copy link
Copy Markdown
Author

j3rbr0wn commented Jan 31, 2024

@Brian-Jaxon if ModelState.IsValid always returns false this could be the reason https://stackoverflow.com/a/72689810

@GeekInTheNorth
Copy link
Copy Markdown

@j3rbr0wn You should stick all this in a Razor Class Library and put it onto the Optimizely Nuget Feed :)

@j3rbr0wn
Copy link
Copy Markdown
Author

@j3rbr0wn You should stick all this in a Razor Class Library and put it onto the Optimizely Nuget Feed :)

@paulmcgann is on it!
https://github.com/paulmcgann/content-cleaner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment