From 6669b54a70e879b855c274318f5856d5c6097531 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 10 Sep 2019 10:50:36 +0100 Subject: [PATCH] Move the logic for finding temp images for persisting into a shared class so Grid & RTE can both use it --- .../PropertyEditors/GridPropertyEditor.cs | 69 ++++++++++++++++--- .../PropertyEditors/RichTextPropertyEditor.cs | 59 +--------------- .../Templates/TemplateUtilities.cs | 57 ++++++++++++++- 3 files changed, 119 insertions(+), 66 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index aa7a1f7355..98c65175af 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -1,14 +1,16 @@ using System.Linq; using Umbraco.Core.Logging; -using Examine; -using Lucene.Net.Documents; using Umbraco.Core; using Umbraco.Core.PropertyEditors; +using Newtonsoft.Json; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; +using Umbraco.Web.Templates; +using Umbraco.Web.Composing; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - using Examine = global::Examine; - /// /// Represents a grid property and parameter editor. /// @@ -22,9 +24,15 @@ namespace Umbraco.Web.PropertyEditors Group = Constants.PropertyEditors.Groups.RichContent)] public class GridPropertyEditor : DataEditor { - public GridPropertyEditor(ILogger logger) + private IMediaService _mediaService; + private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + + public GridPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) : base(logger) - { } + { + _mediaService = mediaService; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; + } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -32,15 +40,58 @@ namespace Umbraco.Web.PropertyEditors /// Overridden to ensure that the value is validated /// /// - protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute); + protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _mediaService, _contentTypeBaseServiceProvider); protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); internal class GridPropertyValueEditor : DataValueEditor { - public GridPropertyValueEditor(DataEditorAttribute attribute) + private IMediaService _mediaService; + private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + + public GridPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) : base(attribute) - { } + { + _mediaService = mediaService; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; + } + + /// + /// Format the data for persistence + /// This to ensure if a RTE is used in a Grid cell/control that we parse it for tmp stored images + /// to persist to the media library when we go to persist this to the DB + /// + /// + /// + /// + public override object FromEditor(ContentPropertyData editorValue, object currentValue) + { + if (editorValue.Value == null) + return null; + + // editorValue.Value is a JSON string of the grid + var rawJson = editorValue.Value.ToString(); + var grid = JsonConvert.DeserializeObject(rawJson); + + // Find all controls that use the RTE editor + var controls = grid.Sections.SelectMany(x => x.Rows.SelectMany(r => r.Areas).SelectMany(a => a.Controls)); + var rtes = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "rte"); + + foreach(var rte in rtes) + { + // Parse the HTML + var html = rte.Value?.ToString(); + + var userId = Current.UmbracoContext.Security.CurrentUser.Id; + + // TODO: In future task(get the parent folder from this config) to save the media into + var parsedHtml = TemplateUtilities.FindAndPersistPastedTempImages(html, -1, userId, _mediaService, _contentTypeBaseServiceProvider); + rte.Value = parsedHtml; + } + + // Convert back to raw JSON for persisting + return JsonConvert.SerializeObject(grid); + } } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 911defcd17..f9267315e4 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -113,64 +113,11 @@ namespace Umbraco.Web.PropertyEditors var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(editorValue.Value.ToString()); var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); - // HTML content when being persisted may j - parsed = FindPastedTempImages(parsed); - - return parsed; - } - - private string FindPastedTempImages(string html) - { - // Find all img's that has data-tmpimg attribute - // Use HTML Agility Pack - https://html-agility-pack.net - var htmlDoc = new HtmlDocument(); - htmlDoc.LoadHtml(html); - - var tmpImages = htmlDoc.DocumentNode.SelectNodes("//img[@data-tmpimg]"); - if (tmpImages == null || tmpImages.Count == 0) - return html; - var userId = Current.UmbracoContext.Security.CurrentUser.Id; - foreach (var img in tmpImages) - { - // The data attribute contains the path to the tmp img to persist as a media item - var tmpImgPath = img.GetAttributeValue("data-tmpimg", string.Empty); - - if (string.IsNullOrEmpty(tmpImgPath)) - continue; - - var absTmpImgPath = IOHelper.MapPath(tmpImgPath); - var fileName = Path.GetFileName(absTmpImgPath); - var safeFileName = fileName.ToSafeFileName(); - - // TODO: In future task (get the parent folder from this config) to save the media into - var mediaItemName = safeFileName.ToFriendlyName(); - var f = _mediaService.CreateMedia(mediaItemName, -1, Constants.Conventions.MediaTypes.Image, userId); - var fileInfo = new FileInfo(absTmpImgPath); - - var fs = fileInfo.OpenReadWithRetry(); - if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); - using (fs) - { - f.SetValue(_contentTypeBaseServiceProvider, Constants.Conventions.Media.File, safeFileName, fs); - } - - _mediaService.Save(f, userId); - - // Add the UDI to the img element as new data attribute - var udi = f.GetUdi(); - img.SetAttributeValue("data-udi", udi.ToString()); - - //Get the new persisted image url - var mediaTyped = Current.UmbracoHelper.Media(f.Id); - var location = mediaTyped.Url; - - // Remove the data attribute (so we do not re-process this) - img.Attributes.Remove("data-tmpimg"); - } - - return htmlDoc.DocumentNode.OuterHtml; + // TODO: In future task(get the parent folder from this config) to save the media into + parsed = TemplateUtilities.FindAndPersistPastedTempImages(parsed, -1, userId, _mediaService, _contentTypeBaseServiceProvider); + return parsed; } } diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index b1d9947b9b..5969951072 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -1,8 +1,11 @@ -using System; +using HtmlAgilityPack; +using System; +using System.IO; using System.Text.RegularExpressions; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Services; using Umbraco.Web.Composing; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; @@ -183,5 +186,57 @@ namespace Umbraco.Web.Templates internal static string RemoveMediaUrlsFromTextString(string text) // see comment in ResolveMediaFromTextString for group reference => ResolveImgPattern.Replace(text, "$1$3$4$5"); + + internal static string FindAndPersistPastedTempImages(string html, int mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) + { + // Find all img's that has data-tmpimg attribute + // Use HTML Agility Pack - https://html-agility-pack.net + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(html); + + var tmpImages = htmlDoc.DocumentNode.SelectNodes("//img[@data-tmpimg]"); + if (tmpImages == null || tmpImages.Count == 0) + return html; + + foreach (var img in tmpImages) + { + // The data attribute contains the path to the tmp img to persist as a media item + var tmpImgPath = img.GetAttributeValue("data-tmpimg", string.Empty); + + if (string.IsNullOrEmpty(tmpImgPath)) + continue; + + var absTmpImgPath = IOHelper.MapPath(tmpImgPath); + var fileName = Path.GetFileName(absTmpImgPath); + var safeFileName = fileName.ToSafeFileName(); + + var mediaItemName = safeFileName.ToFriendlyName(); + var f = mediaService.CreateMedia(mediaItemName, mediaParentFolder, Constants.Conventions.MediaTypes.Image, userId); + var fileInfo = new FileInfo(absTmpImgPath); + + var fs = fileInfo.OpenReadWithRetry(); + if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); + using (fs) + { + f.SetValue(contentTypeBaseServiceProvider, Constants.Conventions.Media.File, safeFileName, fs); + } + + mediaService.Save(f, userId); + + // Add the UDI to the img element as new data attribute + var udi = f.GetUdi(); + img.SetAttributeValue("data-udi", udi.ToString()); + + //Get the new persisted image url + var mediaTyped = Current.UmbracoHelper.Media(f.Id); + var location = mediaTyped.Url; + img.SetAttributeValue("src", location); + + // Remove the data attribute (so we do not re-process this) + img.Attributes.Remove("data-tmpimg"); + } + + return htmlDoc.DocumentNode.OuterHtml; + } } }