Skip to content

Instantly share code, notes, and snippets.

@ronaldbarendse
Last active January 3, 2022 13:01
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 ronaldbarendse/b58bcfcdd68440ccdd316903cd5c8a03 to your computer and use it in GitHub Desktop.
Save ronaldbarendse/b58bcfcdd68440ccdd316903cd5c8a03 to your computer and use it in GitHub Desktop.
Umbraco 8 - Clean-up and prune property data
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
// Enable PublishCleanupComposer after setting up your baseline project
//[assembly: EnableComposer(typeof(PublishCleanupComposer))]
// Gather usage/storage data of your baseline and then ALSO enable PrunePropertyDataComposer
//[assembly: EnableComposer(typeof(PrunePropertyDataComposer))]
[Disable]
[ComposeBefore(typeof(RedirectTrackingComposer))]
public class PublishCleanupComposer : ComponentComposer<PublishCleanupComponent>
{ }
public class PublishCleanupComponent : IComponent
{
private readonly IScopeProvider _scopeProvider;
private readonly IUmbracoContextFactory _umbracoContextFactory;
private readonly IContentService _contentService;
private readonly IMediaService _mediaService;
private readonly IPublishedSnapshotService _publishedSnapshotService;
public PublishCleanupComponent(IScopeProvider scopeProvider, IUmbracoContextFactory umbracoContextFactory, IContentService contentService, IMediaService mediaService, IPublishedSnapshotService publishedSnapshotService)
{
_scopeProvider = scopeProvider;
_umbracoContextFactory = umbracoContextFactory;
_contentService = contentService;
_mediaService = mediaService;
_publishedSnapshotService = publishedSnapshotService;
}
public void Initialize()
{
using (var scope = _scopeProvider.CreateScope(autoComplete: true))
using (var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext())
{
// Re-publish all content
foreach (var rootContent in _contentService.GetRootContent())
{
if (rootContent.Published)
{
_contentService.SaveAndPublishBranch(rootContent, false);
}
}
// Clean up
_contentService.EmptyRecycleBin(-1);
_mediaService.EmptyRecycleBin(-1);
for (int pageIndex = 0; ; pageIndex++)
{
var contents = _contentService.GetPagedDescendants(-1, pageIndex, 1000, out _).ToList();
if (contents.Count == 0)
{
break;
}
foreach (var content in contents)
{
_contentService.DeleteVersions(content.Id, DateTime.MaxValue);
}
}
_publishedSnapshotService.Rebuild();
}
}
public void Terminate()
{ }
}
[Disable]
[ComposeBefore(typeof(PublishCleanupComposer))]
public class PrunePropertyDataComposer : ComponentComposer<PrunePropertyDataComponent>
{ }
public class PrunePropertyDataComponent : IComponent
{
private readonly IUmbracoContextFactory _umbracoContextFactory;
private readonly IScopeProvider _scopeProvider;
private readonly ILocalizationService _localizationService;
private readonly IContentService _contentService;
private readonly IMediaService _mediaService;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IDataTypeService _dataTypeService;
public PrunePropertyDataComponent(IScopeProvider scopeProvider, IUmbracoContextFactory umbracoContextFactory, ILocalizationService localizationService, IContentService contentService, IMediaService mediaService, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService)
{
_scopeProvider = scopeProvider;
_umbracoContextFactory = umbracoContextFactory;
_localizationService = localizationService;
_contentService = contentService;
_mediaService = mediaService;
_propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
}
public void Initialize()
{
using (var scope = _scopeProvider.CreateScope(autoComplete: true))
using (var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext())
{
// Prune content
var cultures = _localizationService.GetAllLanguages().Select(x => x.IsoCode).ToArray();
for (int pageIndex = 0; ; pageIndex++)
{
var contents = _contentService.GetPagedDescendants(-1, pageIndex, 1000, out _).ToList();
if (contents.Count == 0)
{
break;
}
contents.ForEach(x => Prune(x, cultures));
// This only saves a new version: make sure to publish and cleanup old versions afterwards
_contentService.Save(contents);
}
// Prune media
for (int pageIndex = 0; ; pageIndex++)
{
var medias = _mediaService.GetPagedDescendants(-1, pageIndex, 1000, out _).ToList();
if (medias.Count == 0)
{
break;
}
medias.ForEach(x => Prune(x, cultures));
_mediaService.Save(medias);
}
}
}
private void Prune(IContentBase content, string[] cultures)
{
foreach (var property in content.Properties)
{
var propertyCultures = property.PropertyType.VariesByCulture() ? cultures : null;
Prune(content.Key, property, propertyCultures);
}
}
private void Prune(Guid contentKey, Property property, string[] cultures)
{
if (!_propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor))
{
// Editor not found, skip
}
var dataType = _dataTypeService.GetDataType(property.PropertyType.DataTypeId);
var valueEditor = editor.GetValueEditor(dataType.Configuration);
foreach (var culture in (cultures ?? new string[] { null }))
{
var value = property.GetValue(culture);
var editorValue = valueEditor.ToEditor(property, _dataTypeService, culture);
// Handle custom transforms (done by model binding or back-office editors)
if (editorValue != null && valueEditor.ValueType == ValueTypes.Json)
{
if (editorValue is string stringEditorValue)
{
editorValue = string.IsNullOrWhiteSpace(stringEditorValue) ? null : JToken.Parse(stringEditorValue);
}
else
{
editorValue = JToken.FromObject(editorValue);
}
}
else if (editorValue is string[] stringArrayEditorValue)
{
editorValue = JArray.FromObject(stringArrayEditorValue);
}
var databaseValue = valueEditor.FromEditor(new ContentPropertyData(editorValue, dataType.Configuration)
{
ContentKey = contentKey,
PropertyTypeKey = property.PropertyType.Key,
Files = new ContentPropertyFile[0]
}, value);
if (editor.GetTagAttribute() != null &&
databaseValue is IEnumerable<string> tagsValue)
{
// Handle tags property editors (this sets the value internally)
property.AssignTags(tagsValue, false, culture);
}
else
{
property.SetValue(databaseValue, culture);
}
}
}
public void Terminate()
{ }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment