diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 7ab73f3f2d..7ab9f10e1c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -24,6 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal sealed class ContentRepositoryBase
{
///
+ ///
/// This is used for unit tests ONLY
///
public static bool ThrowOnWarning = false;
@@ -43,7 +44,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected ILanguageRepository LanguageRepository { get; }
- protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject
+ protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject ... this causes circular refs, not sure which refs they are though
#region Versions
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs
index f1e2bf20d6..1fa3384d08 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Models;
using Umbraco.Web.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web;
+using Umbraco.Web.Templates;
namespace Umbraco.Tests.PublishedContent
{
@@ -38,9 +39,11 @@ namespace Umbraco.Tests.PublishedContent
base.Initialize();
var converters = Factory.GetInstance();
+ var umbracoCtxAccessor = Mock.Of();
+ var logger = Mock.Of();
var dataTypeService = new TestObjects.TestDataTypeService(
- new DataType(new RichTextPropertyEditor(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of())) { Id = 1 });
+ new DataType(new RichTextPropertyEditor(logger, umbracoCtxAccessor, new MediaParser(umbracoCtxAccessor, logger, Mock.Of(), Mock.Of()))) { Id = 1 });
var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService);
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
index 6ef632bf90..d2f7283ee5 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
@@ -21,6 +21,7 @@ using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
using Umbraco.Web.Models.PublishedContent;
using Umbraco.Web.PropertyEditors;
+using Umbraco.Web.Templates;
namespace Umbraco.Tests.PublishedContent
{
@@ -45,11 +46,12 @@ namespace Umbraco.Tests.PublishedContent
var mediaService = Mock.Of();
var contentTypeBaseServiceProvider = Mock.Of();
var umbracoContextAccessor = Mock.Of();
+ var mediaParser = new MediaParser(umbracoContextAccessor, logger, mediaService, contentTypeBaseServiceProvider);
var dataTypeService = new TestObjects.TestDataTypeService(
new DataType(new VoidEditor(logger)) { Id = 1 },
new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 },
- new DataType(new RichTextPropertyEditor(logger, mediaService, contentTypeBaseServiceProvider, umbracoContextAccessor)) { Id = 1002 },
+ new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, mediaParser)) { Id = 1002 },
new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 },
new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 },
new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 });
diff --git a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs
index 815ce408ee..6cdff240b8 100644
--- a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs
+++ b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs
@@ -79,7 +79,7 @@ namespace Umbraco.Tests.Web
{
var linkParser = new InternalLinkParser(umbracoContextAccessor);
- var output = linkParser.ParseInternalLinks(input);
+ var output = linkParser.EnsureInternalLinks(input);
Assert.AreEqual(result, output);
}
diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
index f782f09289..bec28e33fd 100644
--- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
@@ -28,14 +28,16 @@ namespace Umbraco.Web.PropertyEditors
private IMediaService _mediaService;
private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
private IUmbracoContextAccessor _umbracoContextAccessor;
+ private readonly MediaParser _mediaParser;
private ILogger _logger;
- public GridPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor)
+ public GridPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, MediaParser mediaParser)
: base(logger)
{
_mediaService = mediaService;
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
_umbracoContextAccessor = umbracoContextAccessor;
+ _mediaParser = mediaParser;
_logger = logger;
}
@@ -45,7 +47,7 @@ namespace Umbraco.Web.PropertyEditors
/// Overridden to ensure that the value is validated
///
///
- protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _mediaService, _contentTypeBaseServiceProvider, _umbracoContextAccessor, _logger);
+ protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _mediaService, _contentTypeBaseServiceProvider, _umbracoContextAccessor, _logger, _mediaParser);
protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor();
@@ -55,14 +57,16 @@ namespace Umbraco.Web.PropertyEditors
private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
private IUmbracoContextAccessor _umbracoContextAccessor;
private ILogger _logger;
+ private readonly MediaParser _mediaParser;
- public GridPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger)
+ public GridPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, MediaParser _mediaParser)
: base(attribute)
{
_mediaService = mediaService;
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
_umbracoContextAccessor = umbracoContextAccessor;
_logger = logger;
+ this._mediaParser = _mediaParser;
}
///
@@ -97,8 +101,8 @@ namespace Umbraco.Web.PropertyEditors
// Parse the HTML
var html = rte.Value?.ToString();
- var parseAndSavedTempImages = TemplateUtilities.FindAndPersistPastedTempImages(html, mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger);
- var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(parseAndSavedTempImages);
+ var parseAndSavedTempImages = _mediaParser.FindAndPersistPastedTempImages(html, mediaParentId, userId);
+ var editorValueWithMediaUrlsRemoved = _mediaParser.RemoveImageSources(parseAndSavedTempImages);
rte.Value = editorValueWithMediaUrlsRemoved;
}
@@ -127,7 +131,7 @@ namespace Umbraco.Web.PropertyEditors
{
var html = rte.Value?.ToString();
- var propertyValueWithMediaResolved = TemplateUtilities.ResolveMediaFromTextString(html);
+ var propertyValueWithMediaResolved = _mediaParser.EnsureImageSources(html);
rte.Value = propertyValueWithMediaResolved;
}
diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
index 3eed40c8bf..03dc7b6694 100644
--- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
@@ -24,27 +24,24 @@ namespace Umbraco.Web.PropertyEditors
Icon = "icon-browser-window")]
public class RichTextPropertyEditor : DataEditor
{
- private IMediaService _mediaService;
- private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
private IUmbracoContextAccessor _umbracoContextAccessor;
- private ILogger _logger;
+ private readonly MediaParser _mediaParser;
///
/// The constructor will setup the property editor based on the attribute if one is found
///
- public RichTextPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor) : base(logger)
+ public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, MediaParser mediaParser)
+ : base(logger)
{
- _mediaService = mediaService;
- _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
_umbracoContextAccessor = umbracoContextAccessor;
- _logger = logger;
+ _mediaParser = mediaParser;
}
///
/// Create a custom value editor
///
///
- protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _mediaService, _contentTypeBaseServiceProvider, _umbracoContextAccessor, _logger);
+ protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _mediaParser);
protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor();
@@ -53,20 +50,16 @@ namespace Umbraco.Web.PropertyEditors
///
/// A custom value editor to ensure that macro syntax is parsed when being persisted and formatted correctly for display in the editor
///
- internal class RichTextPropertyValueEditor : DataValueEditor
+ internal class RichTextPropertyValueEditor : DataValueEditor, IDataValueReference
{
- private IMediaService _mediaService;
- private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
private IUmbracoContextAccessor _umbracoContextAccessor;
- private ILogger _logger;
+ private readonly MediaParser _mediaParser;
- public RichTextPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger)
+ public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, MediaParser _mediaParser)
: base(attribute)
{
- _mediaService = mediaService;
- _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
_umbracoContextAccessor = umbracoContextAccessor;
- _logger = logger;
+ this._mediaParser = _mediaParser;
}
///
@@ -98,7 +91,7 @@ namespace Umbraco.Web.PropertyEditors
if (val == null)
return null;
- var propertyValueWithMediaResolved = TemplateUtilities.ResolveMediaFromTextString(val.ToString());
+ var propertyValueWithMediaResolved = _mediaParser.EnsureImageSources(val.ToString());
var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(propertyValueWithMediaResolved, new Dictionary());
return parsed;
}
@@ -120,12 +113,22 @@ namespace Umbraco.Web.PropertyEditors
var mediaParent = config?.MediaParentId;
var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid;
- var parseAndSavedTempImages = TemplateUtilities.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger);
- var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(parseAndSavedTempImages);
+ var parseAndSavedTempImages = _mediaParser.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId);
+ var editorValueWithMediaUrlsRemoved = _mediaParser.RemoveImageSources(parseAndSavedTempImages);
var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved);
return parsed;
}
+
+ ///
+ /// Resolve references from values
+ ///
+ ///
+ ///
+ public IEnumerable GetReferences(object value)
+ {
+ throw new NotImplementedException();
+ }
}
internal class RichTextPropertyIndexValueFactory : IPropertyIndexValueFactory
diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs
index 98413c7b70..578a4cad06 100644
--- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs
+++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs
@@ -13,10 +13,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
public class MarkdownEditorValueConverter : PropertyValueConverterBase
{
private readonly InternalLinkParser _localLinkParser;
+ private readonly UrlParser _urlResolver;
- public MarkdownEditorValueConverter(InternalLinkParser localLinkParser)
+ public MarkdownEditorValueConverter(InternalLinkParser localLinkParser, UrlParser urlResolver)
{
_localLinkParser = localLinkParser;
+ _urlResolver = urlResolver;
}
public override bool IsConverter(IPublishedPropertyType propertyType)
@@ -34,8 +36,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
var sourceString = source.ToString();
// ensures string is parsed for {localLink} and urls are resolved correctly
- sourceString = _localLinkParser.ParseInternalLinks(sourceString, preview);
- sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString);
+ sourceString = _localLinkParser.EnsureInternalLinks(sourceString, preview);
+ sourceString = _urlResolver.EnsureUrls(sourceString);
return sourceString;
}
diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs
index 95cf2cfc85..88c1429b16 100644
--- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs
+++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs
@@ -25,6 +25,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IMacroRenderer _macroRenderer;
private readonly InternalLinkParser _internalLinkParser;
+ private readonly UrlParser _urlResolver;
+ private readonly MediaParser _mediaParser;
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
{
@@ -33,11 +35,14 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
return PropertyCacheLevel.Snapshot;
}
- public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, InternalLinkParser internalLinkParser)
+ public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer,
+ InternalLinkParser internalLinkParser, UrlParser urlResolver, MediaParser mediaParser)
{
_umbracoContextAccessor = umbracoContextAccessor;
_macroRenderer = macroRenderer;
_internalLinkParser = internalLinkParser;
+ _urlResolver = urlResolver;
+ _mediaParser = mediaParser;
}
// NOT thread-safe over a request because it modifies the
@@ -83,9 +88,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
var sourceString = source.ToString();
// ensures string is parsed for {localLink} and urls and media are resolved correctly
- sourceString = _internalLinkParser.ParseInternalLinks(sourceString, preview);
- sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString);
- sourceString = TemplateUtilities.ResolveMediaFromTextString(sourceString);
+ sourceString = _internalLinkParser.EnsureInternalLinks(sourceString, preview);
+ sourceString = _urlResolver.EnsureUrls(sourceString);
+ sourceString = _mediaParser.EnsureImageSources(sourceString);
// ensure string is parsed for macros and macros are executed correctly
sourceString = RenderRteMacros(sourceString, preview);
diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs
index ee49536d9d..1b85d6e608 100644
--- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs
+++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs
@@ -11,9 +11,10 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
[DefaultPropertyValueConverter]
public class TextStringValueConverter : PropertyValueConverterBase
{
- public TextStringValueConverter(InternalLinkParser internalLinkParser)
+ public TextStringValueConverter(InternalLinkParser internalLinkParser, UrlParser urlParser)
{
_internalLinkParser = internalLinkParser;
+ _urlParser = urlParser;
}
private static readonly string[] PropertyTypeAliases =
@@ -22,6 +23,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
Constants.PropertyEditors.Aliases.TextArea
};
private readonly InternalLinkParser _internalLinkParser;
+ private readonly UrlParser _urlParser;
public override bool IsConverter(IPublishedPropertyType propertyType)
=> PropertyTypeAliases.Contains(propertyType.EditorAlias);
@@ -38,8 +40,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
var sourceString = source.ToString();
// ensures string is parsed for {localLink} and urls are resolved correctly
- sourceString = _internalLinkParser.ParseInternalLinks(sourceString, preview);
- sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString);
+ sourceString = _internalLinkParser.EnsureInternalLinks(sourceString, preview);
+ sourceString = _urlParser.EnsureUrls(sourceString);
return sourceString;
}
diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
index 82137bbd9d..1b3128388d 100644
--- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs
+++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
@@ -108,6 +108,8 @@ namespace Umbraco.Web.Runtime
composition.RegisterUnique();
composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
// register the umbraco helper - this is Transient! very important!
// also, if not level.Run, we cannot really use the helper (during upgrade...)
diff --git a/src/Umbraco.Web/Templates/InternalLinkParser.cs b/src/Umbraco.Web/Templates/InternalLinkParser.cs
index 0d8a480a90..32d7d42eac 100644
--- a/src/Umbraco.Web/Templates/InternalLinkParser.cs
+++ b/src/Umbraco.Web/Templates/InternalLinkParser.cs
@@ -23,17 +23,23 @@ namespace Umbraco.Web.Templates
_umbracoContextAccessor = umbracoContextAccessor;
}
- public string ParseInternalLinks(string text, bool preview)
+ ///
+ /// Parses the string looking for the {localLink} syntax and updates them to their correct links.
+ ///
+ ///
+ ///
+ ///
+ public string EnsureInternalLinks(string text, bool preview)
{
if (_umbracoContextAccessor.UmbracoContext == null)
throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext");
if (!preview)
- return ParseInternalLinks(text);
+ return EnsureInternalLinks(text);
using (_umbracoContextAccessor.UmbracoContext.ForcedPreview(preview)) // force for url provider
{
- return ParseInternalLinks(text);
+ return EnsureInternalLinks(text);
}
}
@@ -43,7 +49,7 @@ namespace Umbraco.Web.Templates
///
///
///
- public string ParseInternalLinks(string text)
+ public string EnsureInternalLinks(string text)
{
if (_umbracoContextAccessor.UmbracoContext == null)
throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext");
diff --git a/src/Umbraco.Web/Templates/MediaParser.cs b/src/Umbraco.Web/Templates/MediaParser.cs
new file mode 100644
index 0000000000..9a3f8def3c
--- /dev/null
+++ b/src/Umbraco.Web/Templates/MediaParser.cs
@@ -0,0 +1,186 @@
+using HtmlAgilityPack;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.RegularExpressions;
+using Umbraco.Core;
+using Umbraco.Core.Exceptions;
+using Umbraco.Core.IO;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Web.Templates
+{
+ public sealed class MediaParser
+ {
+ public MediaParser(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider)
+ {
+ _umbracoContextAccessor = umbracoContextAccessor;
+ _logger = logger;
+ _mediaService = mediaService;
+ _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
+ }
+
+ private static readonly Regex ResolveImgPattern = new Regex(@"(
]*src="")([^""\?]*)([^""]*""[^>]*data-udi="")([^""]*)(""[^>]*>)",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
+ private readonly IUmbracoContextAccessor _umbracoContextAccessor;
+ private readonly ILogger _logger;
+ private readonly IMediaService _mediaService;
+ private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
+ const string TemporaryImageDataAttribute = "data-tmpimg";
+
+ ///
+ /// Parses the string looking for Umbraco image tags and updates them to their up-to-date image sources.
+ ///
+ ///
+ ///
+ /// Umbraco image tags are identified by their data-udi attributes
+ public string EnsureImageSources(string text)
+ {
+ // don't attempt to proceed without a context
+ if (_umbracoContextAccessor?.UmbracoContext?.Media == null)
+ {
+ return text;
+ }
+
+ return ResolveImgPattern.Replace(text, match =>
+ {
+ // match groups:
+ // - 1 = from the beginning of the image tag until src attribute value begins
+ // - 2 = the src attribute value excluding the querystring (if present)
+ // - 3 = anything after group 2 and before the data-udi attribute value begins
+ // - 4 = the data-udi attribute value
+ // - 5 = anything after group 4 until the image tag is closed
+ var udi = match.Groups[4].Value;
+ if (udi.IsNullOrWhiteSpace() || GuidUdi.TryParse(udi, out var guidUdi) == false)
+ {
+ return match.Value;
+ }
+ var media = _umbracoContextAccessor?.UmbracoContext?.Media.GetById(guidUdi.Guid);
+ if (media == null)
+ {
+ // image does not exist - we could choose to remove the image entirely here (return empty string),
+ // but that would leave the editors completely in the dark as to why the image doesn't show
+ return match.Value;
+ }
+
+ var url = media.Url;
+ return $"{match.Groups[1].Value}{url}{match.Groups[3].Value}{udi}{match.Groups[5].Value}";
+ });
+ }
+
+ ///
+ /// Removes media urls from <img> tags where a data-udi attribute is present
+ ///
+ ///
+ ///
+ internal string RemoveImageSources(string text)
+ // see comment in ResolveMediaFromTextString for group reference
+ => ResolveImgPattern.Replace(text, "$1$3$4$5");
+
+ internal string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId)
+ {
+ // 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[@{TemporaryImageDataAttribute}]");
+ if (tmpImages == null || tmpImages.Count == 0)
+ return html;
+
+ // An array to contain a list of URLs that
+ // we have already processed to avoid dupes
+ var uploadedImages = new Dictionary();
+
+ 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(TemporaryImageDataAttribute, string.Empty);
+
+ if (string.IsNullOrEmpty(tmpImgPath))
+ continue;
+
+ var absoluteTempImagePath = IOHelper.MapPath(tmpImgPath);
+ var fileName = Path.GetFileName(absoluteTempImagePath);
+ var safeFileName = fileName.ToSafeFileName();
+
+ var mediaItemName = safeFileName.ToFriendlyName();
+ IMedia mediaFile;
+ GuidUdi udi;
+
+ if (uploadedImages.ContainsKey(tmpImgPath) == false)
+ {
+ if (mediaParentFolder == Guid.Empty)
+ mediaFile = _mediaService.CreateMedia(mediaItemName, Constants.System.Root, Constants.Conventions.MediaTypes.Image, userId);
+ else
+ mediaFile = _mediaService.CreateMedia(mediaItemName, mediaParentFolder, Constants.Conventions.MediaTypes.Image, userId);
+
+ var fileInfo = new FileInfo(absoluteTempImagePath);
+
+ var fileStream = fileInfo.OpenReadWithRetry();
+ if (fileStream == null) throw new InvalidOperationException("Could not acquire file stream");
+ using (fileStream)
+ {
+ mediaFile.SetValue(_contentTypeBaseServiceProvider, Constants.Conventions.Media.File, safeFileName, fileStream);
+ }
+
+ _mediaService.Save(mediaFile, userId);
+
+ udi = mediaFile.GetUdi();
+ }
+ else
+ {
+ // Already been uploaded & we have it's UDI
+ udi = uploadedImages[tmpImgPath];
+ }
+
+ // Add the UDI to the img element as new data attribute
+ img.SetAttributeValue("data-udi", udi.ToString());
+
+ // Get the new persisted image url
+ var mediaTyped = _umbracoContextAccessor?.UmbracoContext?.Media.GetById(udi.Guid);
+ if (mediaTyped == null)
+ throw new PanicException($"Could not find media by id {udi.Guid} or there was no UmbracoContext available.");
+
+ var location = mediaTyped.Url;
+
+ // Find the width & height attributes as we need to set the imageprocessor QueryString
+ var width = img.GetAttributeValue("width", int.MinValue);
+ var height = img.GetAttributeValue("height", int.MinValue);
+
+ if (width != int.MinValue && height != int.MinValue)
+ {
+ location = $"{location}?width={width}&height={height}&mode=max";
+ }
+
+ img.SetAttributeValue("src", location);
+
+ // Remove the data attribute (so we do not re-process this)
+ img.Attributes.Remove(TemporaryImageDataAttribute);
+
+ // Add to the dictionary to avoid dupes
+ if (uploadedImages.ContainsKey(tmpImgPath) == false)
+ {
+ uploadedImages.Add(tmpImgPath, udi);
+
+ // Delete folder & image now its saved in media
+ // The folder should contain one image - as a unique guid folder created
+ // for each image uploaded from TinyMceController
+ var folderName = Path.GetDirectoryName(absoluteTempImagePath);
+ try
+ {
+ Directory.Delete(folderName, true);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(typeof(MediaParser), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath);
+ }
+ }
+ }
+
+ return htmlDoc.DocumentNode.OuterHtml;
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs
index 1092be73e2..d4bae38147 100644
--- a/src/Umbraco.Web/Templates/TemplateUtilities.cs
+++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Text.RegularExpressions;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
@@ -16,19 +15,9 @@ using File = System.IO.File;
namespace Umbraco.Web.Templates
{
- ///
- /// Utility class used for templates
- ///
+ [Obsolete("This class is obsolete, all methods have been moved to other classes such as InternalLinkHelper, UrlResolver and MediaParser")]
public static class TemplateUtilities
{
- const string TemporaryImageDataAttribute = "data-tmpimg";
-
- private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?",
- RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
-
- private static readonly Regex ResolveImgPattern = new Regex(@"(
]*src="")([^""\?]*)([^""]*""[^>]*data-udi="")([^""]*)(""[^>]*>)",
- RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
-
[Obsolete("Inject and use an instance of InternalLinkParser instead")]
internal static string ParseInternalLinks(string text, bool preview, UmbracoContext umbracoContext)
{
@@ -41,201 +30,27 @@ namespace Umbraco.Web.Templates
}
[Obsolete("Inject and use an instance of InternalLinkParser instead")]
- public static string ParseInternalLinks(string text, UrlProvider urlProvider) =>
- Current.Factory.GetInstance().ParseInternalLinks(text);
+ public static string ParseInternalLinks(string text, UrlProvider urlProvider)
+ => Current.Factory.GetInstance().EnsureInternalLinks(text);
- ///
- /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path.
- ///
- ///
- ///
- ///
- /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly.
- /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs.
- ///
+ [Obsolete("Inject and use an instance of UrlResolver")]
public static string ResolveUrlsFromTextString(string text)
- {
- if (Current.Configs.Settings().Content.ResolveUrlsFromTextString == false) return text;
-
- using (var timer = Current.ProfilingLogger.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete"))
- {
- // find all relative urls (ie. urls that contain ~)
- var tags = ResolveUrlPattern.Matches(text);
- Current.Logger.Debug(typeof(IOHelper), "After regex: {Duration} matched: {TagsCount}", timer.Stopwatch.ElapsedMilliseconds, tags.Count);
- foreach (Match tag in tags)
- {
- var url = "";
- if (tag.Groups[1].Success)
- url = tag.Groups[1].Value;
-
- // The richtext editor inserts a slash in front of the url. That's why we need this little fix
- // if (url.StartsWith("/"))
- // text = text.Replace(url, ResolveUrl(url.Substring(1)));
- // else
- if (String.IsNullOrEmpty(url) == false)
- {
- var resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url);
- text = text.Replace(url, resolvedUrl);
- }
- }
- }
-
- return text;
- }
+ => Current.Factory.GetInstance().EnsureUrls(text);
+ [Obsolete("Use StringExtensions.CleanForXss instead")]
public static string CleanForXss(string text, params char[] ignoreFromClean)
- {
- return text.CleanForXss(ignoreFromClean);
- }
+ => text.CleanForXss(ignoreFromClean);
- ///
- /// Parses the string looking for Umbraco image tags and updates them to their up-to-date image sources.
- ///
- ///
- ///
- /// Umbraco image tags are identified by their data-udi attributes
+ [Obsolete("Use MediaParser.EnsureImageSources instead")]
public static string ResolveMediaFromTextString(string text)
- {
- // don't attempt to proceed without a context
- if (Current.UmbracoContext == null || Current.UmbracoContext.Media == null)
- {
- return text;
- }
-
- return ResolveImgPattern.Replace(text, match =>
- {
- // match groups:
- // - 1 = from the beginning of the image tag until src attribute value begins
- // - 2 = the src attribute value excluding the querystring (if present)
- // - 3 = anything after group 2 and before the data-udi attribute value begins
- // - 4 = the data-udi attribute value
- // - 5 = anything after group 4 until the image tag is closed
- var udi = match.Groups[4].Value;
- if(udi.IsNullOrWhiteSpace() || GuidUdi.TryParse(udi, out var guidUdi) == false)
- {
- return match.Value;
- }
- var media = Current.UmbracoContext.Media.GetById(guidUdi.Guid);
- if(media == null)
- {
- // image does not exist - we could choose to remove the image entirely here (return empty string),
- // but that would leave the editors completely in the dark as to why the image doesn't show
- return match.Value;
- }
-
- var url = media.Url;
- return $"{match.Groups[1].Value}{url}{match.Groups[3].Value}{udi}{match.Groups[5].Value}";
- });
- }
-
- ///
- /// Removes media urls from <img> tags where a data-udi attribute is present
- ///
- ///
- ///
+ => Current.Factory.GetInstance().EnsureImageSources(text);
+
+ [Obsolete("Use MediaParser.RemoveImageSources instead")]
internal static string RemoveMediaUrlsFromTextString(string text)
- // see comment in ResolveMediaFromTextString for group reference
- => ResolveImgPattern.Replace(text, "$1$3$4$5");
+ => Current.Factory.GetInstance().RemoveImageSources(text);
+ [Obsolete("Use MediaParser.RemoveImageSources instead")]
internal static string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ILogger logger)
- {
- // 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[@{TemporaryImageDataAttribute}]");
- if (tmpImages == null || tmpImages.Count == 0)
- return html;
-
- // An array to contain a list of URLs that
- // we have already processed to avoid dupes
- var uploadedImages = new Dictionary();
-
- 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(TemporaryImageDataAttribute, string.Empty);
-
- if (string.IsNullOrEmpty(tmpImgPath))
- continue;
-
- var absoluteTempImagePath = IOHelper.MapPath(tmpImgPath);
- var fileName = Path.GetFileName(absoluteTempImagePath);
- var safeFileName = fileName.ToSafeFileName();
-
- var mediaItemName = safeFileName.ToFriendlyName();
- IMedia mediaFile;
- GuidUdi udi;
-
- if (uploadedImages.ContainsKey(tmpImgPath) == false)
- {
- if (mediaParentFolder == Guid.Empty)
- mediaFile = mediaService.CreateMedia(mediaItemName, Constants.System.Root, Constants.Conventions.MediaTypes.Image, userId);
- else
- mediaFile = mediaService.CreateMedia(mediaItemName, mediaParentFolder, Constants.Conventions.MediaTypes.Image, userId);
-
- var fileInfo = new FileInfo(absoluteTempImagePath);
-
- var fileStream = fileInfo.OpenReadWithRetry();
- if (fileStream == null) throw new InvalidOperationException("Could not acquire file stream");
- using (fileStream)
- {
- mediaFile.SetValue(contentTypeBaseServiceProvider, Constants.Conventions.Media.File, safeFileName, fileStream);
- }
-
- mediaService.Save(mediaFile, userId);
-
- udi = mediaFile.GetUdi();
- }
- else
- {
- // Already been uploaded & we have it's UDI
- udi = uploadedImages[tmpImgPath];
- }
-
- // Add the UDI to the img element as new data attribute
- img.SetAttributeValue("data-udi", udi.ToString());
-
- // Get the new persisted image url
- var mediaTyped = Current.UmbracoHelper.Media(udi.Guid);
- var location = mediaTyped.Url;
-
- // Find the width & height attributes as we need to set the imageprocessor QueryString
- var width = img.GetAttributeValue("width", int.MinValue);
- var height = img.GetAttributeValue("height", int.MinValue);
-
- if(width != int.MinValue && height != int.MinValue)
- {
- location = $"{location}?width={width}&height={height}&mode=max";
- }
-
- img.SetAttributeValue("src", location);
-
- // Remove the data attribute (so we do not re-process this)
- img.Attributes.Remove(TemporaryImageDataAttribute);
-
- // Add to the dictionary to avoid dupes
- if(uploadedImages.ContainsKey(tmpImgPath) == false)
- {
- uploadedImages.Add(tmpImgPath, udi);
-
- // Delete folder & image now its saved in media
- // The folder should contain one image - as a unique guid folder created
- // for each image uploaded from TinyMceController
- var folderName = Path.GetDirectoryName(absoluteTempImagePath);
- try
- {
- Directory.Delete(folderName, true);
- }
- catch (Exception ex)
- {
- logger.Error(typeof(TemplateUtilities), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath);
- }
- }
- }
-
- return htmlDoc.DocumentNode.OuterHtml;
- }
+ => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId);
}
}
diff --git a/src/Umbraco.Web/Templates/UrlParser.cs b/src/Umbraco.Web/Templates/UrlParser.cs
new file mode 100644
index 0000000000..e5c40b7365
--- /dev/null
+++ b/src/Umbraco.Web/Templates/UrlParser.cs
@@ -0,0 +1,61 @@
+using System.Text.RegularExpressions;
+using Umbraco.Core.Configuration.UmbracoSettings;
+using Umbraco.Core.IO;
+using Umbraco.Core.Logging;
+
+namespace Umbraco.Web.Templates
+{
+ public sealed class UrlParser
+ {
+ private readonly IContentSection _contentSection;
+ private readonly IProfilingLogger _logger;
+
+ private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
+
+ public UrlParser(IContentSection contentSection, IProfilingLogger logger)
+ {
+ _contentSection = contentSection;
+ _logger = logger;
+ }
+
+ ///
+ /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path.
+ ///
+ ///
+ ///
+ ///
+ /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly.
+ /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs.
+ ///
+ public string EnsureUrls(string text)
+ {
+ if (_contentSection.ResolveUrlsFromTextString == false) return text;
+
+ using (var timer = _logger.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete"))
+ {
+ // find all relative urls (ie. urls that contain ~)
+ var tags = ResolveUrlPattern.Matches(text);
+ _logger.Debug(typeof(IOHelper), "After regex: {Duration} matched: {TagsCount}", timer.Stopwatch.ElapsedMilliseconds, tags.Count);
+ foreach (Match tag in tags)
+ {
+ var url = "";
+ if (tag.Groups[1].Success)
+ url = tag.Groups[1].Value;
+
+ // The richtext editor inserts a slash in front of the url. That's why we need this little fix
+ // if (url.StartsWith("/"))
+ // text = text.Replace(url, ResolveUrl(url.Substring(1)));
+ // else
+ if (string.IsNullOrEmpty(url) == false)
+ {
+ var resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url);
+ text = text.Replace(url, resolvedUrl);
+ }
+ }
+ }
+
+ return text;
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 616ed908e1..74ac3f65f3 100755
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -248,6 +248,8 @@
+
+
diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs
index 805b9267f9..c0f83fd1af 100644
--- a/src/Umbraco.Web/UmbracoComponentRenderer.cs
+++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs
@@ -159,7 +159,7 @@ namespace Umbraco.Web
_umbracoContextAccessor.UmbracoContext.HttpContext.Response.ContentType = contentType;
//Now, we need to ensure that local links are parsed
- html = _internalLinkParser.ParseInternalLinks(output.ToString());
+ html = _internalLinkParser.EnsureInternalLinks(output.ToString());
}
}