Skip to content

Instantly share code, notes, and snippets.

@auroris
Last active September 24, 2020 15:54
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 auroris/e351ca2d1cfe132cfa58208eef0ec0d5 to your computer and use it in GitHub Desktop.
Save auroris/e351ca2d1cfe132cfa58208eef0ec0d5 to your computer and use it in GitHub Desktop.
An Umbraco 8 enhancement that converts a base64 encoded image into a proper media image. This file is a basic class that expects to be placed in an App_Code folder and compiled along with your Umbraco project.
using HtmlAgilityPack;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
namespace Umbraco.App_Code
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class Base64MediaConverterComposer : ComponentComposer<Base64MediaConverterComponent>
{ }
public class Base64MediaConverterComponent : IComponent
{
public void Initialize()
{
// Subscribe to Umbraco events
ContentService.Saving += ContentService_Saving;
}
private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e)
{
// Iterate through all values being saved
foreach (IContent savedEntity in e.SavedEntities)
{
foreach (Property prop in savedEntity.Properties)
{
foreach (Property.PropertyValue val in prop.Values)
{
// If the value is a Grid editor
if (prop.PropertyType.PropertyEditorAlias.Equals("Umbraco.Grid"))
{
// Load the JSON document
JObject doc = JObject.Parse(val.EditedValue as String);
// Find the "controls" elements which specifies subdocuments in a grid's row/column location
List<JToken> controlGroup = doc.Descendants().Where(x => x is JObject && x["controls"] != null).ToList();
// For each row/column location with at least one control
foreach (var controls in controlGroup)
{
// For each control in a location
foreach (var control in controls["controls"])
{
// If the control is type "rte" then we do stuff; else, ignore
// Other control types like "image" or "macro" are irrelevant for this purpose
// see config\grid.editors.config.js for control aliases
if (control["editor"]["alias"].ToString().Equals("rte"))
{
try
{
if ((String)control["value"] != null && ((String)control["value"]).IndexOf("data:", StringComparison.OrdinalIgnoreCase) > -1)
{
// Replace the sub document in the json
control["value"] = ReplaceBase64((String)control["value"], savedEntity);
}
}
catch (Exception ex)
{
// Log the error, bubble up message to back office
Current.Logger.Error(this.GetType(), "Error converting base64 image: " + ex.ToString(), ex);
e.Cancel = true;
e.Messages.Add(new EventMessage("Error converting image", "An error occured when attempting to convert an embedded image into a media library image.", EventMessageType.Error));
}
}
}
}
// Replace the JSON document
prop.SetValue(doc.ToString(), val.Culture, val.Segment);
}
// If the value is a TinyMCE editor
if (prop.PropertyType.PropertyEditorAlias.Equals("Umbraco.TinyMCE"))
{
try
{
if (val.EditedValue != null && ((String)val.EditedValue).IndexOf("data:", StringComparison.OrdinalIgnoreCase) > -1)
{
// Replace the html document
prop.SetValue(ReplaceBase64(val.EditedValue as String, savedEntity), val.Culture, val.Segment);
}
}
catch (Exception ex)
{
// Log the error, bubble up message to back office
Current.Logger.Error(this.GetType(), "Error converting base64 image: " + ex.ToString(), ex);
e.Cancel = true;
e.Messages.Add(new EventMessage("Error converting image", "An error occured when attempting to convert an embedded image into a media library image.", EventMessageType.Error));
}
}
}
}
}
}
/// <summary>
/// Saves a base64 encoded image into the image library and then replaces the img src with the location of the image
/// </summary>
/// <param name="content">An HTML document fragment</param>
/// <param name="entity">The content entity the image belongs to</param>
/// <returns>An updated HTML document fragment</returns>
String ReplaceBase64(String content, IUmbracoEntity entity)
{
if (content == null || entity == null) { return content; }
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(content);
// For every image
foreach (HtmlNode node in doc.DocumentNode.Descendants("img"))
{
// Is it a data url?
if (node.Attributes["src"] != null && node.Attributes["src"].Value.IndexOf("data:", StringComparison.OrdinalIgnoreCase) > -1)
{
// Convert the data url
String[] data = node.Attributes["src"].Value.Split(new[] { ':', ';', ',' });
String fileMime = data[1];
String fileName = Guid.NewGuid().ToString() + "." + fileMime.Split('/')[1];
byte[] fileBytes = Convert.FromBase64String(data[3]);
// Umbraco save image
IMedia image = Current.Services.MediaService.CreateMedia(fileName, Constants.System.Root, Constants.Conventions.MediaTypes.Image);
image.SetValue(Current.Services.ContentTypeBaseServices, Constants.Conventions.Media.File, fileName, new MemoryStream(fileBytes));
Current.Services.MediaService.Save(image);
// Tell Umbraco that the document uses the image
Current.Services.RelationService.Relate(entity, image, Constants.Conventions.RelationTypes.RelatedMediaAlias);
// Update img src
node.SetAttributeValue("src", image.GetUrl(Constants.Conventions.Media.File, Current.Logger));
}
}
// Return the html document fragment
return doc.DocumentNode.OuterHtml;
}
public void Terminate()
{
//unsubscribe during shutdown
ContentService.Saving -= ContentService_Saving;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment