Skip to content

Instantly share code, notes, and snippets.

@danlister
Last active November 8, 2021 17:35
Show Gist options
  • Save danlister/2c3ceb1a66a706ef0913 to your computer and use it in GitHub Desktop.
Save danlister/2c3ceb1a66a706ef0913 to your computer and use it in GitHub Desktop.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Web.Script.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
namespace MyApplication.EventHandlers
{
/// <summary>
/// Image Cropper update handler to attach on to the
/// Data Type Service Saved event. When a data type is saved and
/// is of type Umbraco.ImageCropper, all content, media and member
/// items using that data type will have their image crops synced
/// to the pre value crops stored in the data type being saved.
/// </summary>
public class ImageCropperUpdateHandler : ApplicationEventHandler
{
public ImageCropperUpdateHandler()
{
DataTypeService.Saved += DataTypeServiceOnSaved;
}
private static void DataTypeServiceOnSaved(IDataTypeService sender, SaveEventArgs<IDataTypeDefinition> args)
{
foreach (var dataTypeDef in args.SavedEntities
.Where(d => d.PropertyEditorAlias.Equals(Umbraco.Core.Constants.PropertyEditors.ImageCropperAlias)))
{
// Find the list of crops for the current data type
//
// BUG?: There seems to be an issue with the Image Cropper
// data type when it's saved. Sometimes You have to save the data
// type twice for a new crop to be added or a crop removed.
var crops = FindCrops(dataTypeDef);
// Find any types which use the current image
// cropper data type
var types = FindTypesUsingPropertyEditor(dataTypeDef.PropertyEditorAlias);
foreach (var type in types)
{
// For each type, we need to find all
// instances (content, media and members) of that type
// and update each
var contentBaseItems = FindContentBaseItems(type.Id);
foreach (var contentBaseItem in contentBaseItems)
UpdateContentBaseItem(contentBaseItem, dataTypeDef.Id, crops);
}
}
}
private static IEnumerable<PreValueImageCrop> FindCrops(IDataTypeDefinition dataTypeDef)
{
var retval = new List<PreValueImageCrop>();
var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesByDataTypeId(dataTypeDef.Id);
// Loop through each prevalue and try to serialize as
// a Pre Value Image Crop list. If we can't, we assume that
// the prevalue is not the correct image crop list.
foreach (var preValue in preValues)
{
var imageCrops = new JavaScriptSerializer().Deserialize<List<PreValueImageCrop>>(preValue);
if (imageCrops != default(List<PreValueImageCrop>))
{
retval.AddRange(imageCrops);
}
}
return retval;
}
private static IEnumerable<IContentTypeBase> FindTypesUsingPropertyEditor(string alias)
{
var typesUsingCurrentDef = new List<IContentTypeBase>();
var types = new List<IContentTypeBase>();
types.AddRange(ApplicationContext.Current.Services.ContentTypeService.GetAllContentTypes());
types.AddRange(ApplicationContext.Current.Services.ContentTypeService.GetAllMediaTypes());
types.AddRange(ApplicationContext.Current.Services.MemberTypeService.GetAll());
foreach (var type in types)
{
foreach (var propertyType in type.PropertyTypes)
{
if (!propertyType.PropertyEditorAlias.Equals(alias))
continue;
typesUsingCurrentDef.Add(type);
}
}
return typesUsingCurrentDef;
}
private static IEnumerable<IContentBase> FindContentBaseItems(int typeId)
{
var retval = new List<IContentBase>();
retval.AddRange(ApplicationContext.Current.Services.ContentService.GetContentOfContentType(typeId));
retval.AddRange(ApplicationContext.Current.Services.MediaService.GetMediaOfMediaType(typeId));
retval.AddRange(ApplicationContext.Current.Services.MemberService.GetMembersByMemberType(typeId));
return retval;
}
private static void UpdateContentBaseItem(IContentBase item, int dataTypeDefId, IEnumerable<PreValueImageCrop> crops)
{
var updated = false;
foreach (var propertyType in item.PropertyTypes)
{
if (!propertyType.DataTypeDefinitionId.Equals(dataTypeDefId))
continue;
// We've found a property which uses the current image cropper.
// So lets make sure the property value has all the crops.
var property = item.Properties.FirstOrDefault(p => p.Alias.Equals(propertyType.Alias));
if (property == null || property.Value == null || string.IsNullOrWhiteSpace(property.Value.ToString()))
continue;
var propertyValue = JObject.Parse(property.Value.ToString());
var propertyCrops = propertyValue["crops"].Value<JArray>();
updated = SyncCrops(propertyCrops, crops);
if (!updated)
continue;
// If we've updated the current property value
// make sure we update the actual property value as well.
propertyValue["crops"] = propertyCrops;
property.Value = propertyValue.ToString();
}
if (!updated)
return;
// Based on the content base type, save through
// the correct service
var type = ApplicationContext.Current.Services.EntityService.GetObjectType(item);
switch (type)
{
case UmbracoObjectTypes.Document:
ApplicationContext.Current.Services.ContentService.Save(item as IContent);
break;
case UmbracoObjectTypes.Media:
ApplicationContext.Current.Services.MediaService.Save(item as IMedia);
break;
case UmbracoObjectTypes.Member:
ApplicationContext.Current.Services.MemberService.Save(item as IMember);
break;
}
}
private static bool SyncCrops(ICollection<JToken> propertyCrops, IEnumerable<PreValueImageCrop> crops)
{
var retval = false;
// Add crops to the property crops collection
// which are missing
foreach (var crop in crops)
{
var cropPresent = false;
// Loop through the crops in the property value
// to see if the current crop is present
foreach (var propertyCrop in propertyCrops)
{
var alias = propertyCrop["alias"].Value<string>();
if (!alias.Equals(crop.Alias))
continue;
cropPresent = true;
break;
}
if (cropPresent)
continue;
// If the current crop is not present, we need
// to add it to the property value crops array
var cropString =
string.Format("{{\"alias\": \"{0}\", \"width\": {1}, \"height\": {2}}}",
crop.Alias,
crop.Width,
crop.Height);
// There must be a better way to do this?
var toAdd = (JObject) JToken.ReadFrom(new JsonTextReader(new StringReader(cropString)));
propertyCrops.Add(toAdd);
retval = true;
}
var cropsToRemove = new List<JToken>();
// Find any crops to remove
foreach (var propertyCrop in propertyCrops)
{
var alias = propertyCrop["alias"].Value<string>();
if (crops.Any(crop => crop.Alias.Equals(alias)))
continue;
cropsToRemove.Add(propertyCrop);
}
if (!cropsToRemove.Any())
return retval;
// Remove crops which are not in the pre value
// crop list
foreach (var cropToRemove in cropsToRemove)
propertyCrops.Remove(cropToRemove);
return true;
}
}
/// <summary>
/// Used to handle pre value image crops.
/// </summary>
public class PreValueImageCrop
{
[DataMember(Name = "alias")]
public string Alias { get; set; }
[DataMember(Name = "width")]
public int Width { get; set; }
[DataMember(Name = "height")]
public int Height { get; set; }
}
}
@markadrake
Copy link

@s6admin I had this very same issue because I updated an existing data type (image upload) to (image cropper) and did not re-save the media afterwards. So instead of getting back JSON I got back the basic string URL.

Luckily I only had one image in the media library at the time - so I clicked the save button (after the change from file upload to image cropper) and now this class is getting the JSON back it expects.

I don't know what the solution is for a media library that is full of images and this method is not practical. Try the normal things - clear the cache, restart the server.

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