No more using TemplateUtilities
This commit is contained in:
@@ -24,6 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
internal sealed class ContentRepositoryBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// This is used for unit tests ONLY
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
|
||||
@@ -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<PropertyValueConverterCollection>();
|
||||
var umbracoCtxAccessor = Mock.Of<IUmbracoContextAccessor>();
|
||||
var logger = Mock.Of<ILogger>();
|
||||
|
||||
var dataTypeService = new TestObjects.TestDataTypeService(
|
||||
new DataType(new RichTextPropertyEditor(Mock.Of<ILogger>(), Mock.Of<IMediaService>(), Mock.Of<IContentTypeBaseServiceProvider>(), Mock.Of<IUmbracoContextAccessor>())) { Id = 1 });
|
||||
new DataType(new RichTextPropertyEditor(logger, umbracoCtxAccessor, new MediaParser(umbracoCtxAccessor, logger, Mock.Of<IMediaService>(), Mock.Of<IContentTypeBaseServiceProvider>()))) { Id = 1 });
|
||||
|
||||
var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), converters, dataTypeService);
|
||||
|
||||
|
||||
@@ -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<IMediaService>();
|
||||
var contentTypeBaseServiceProvider = Mock.Of<IContentTypeBaseServiceProvider>();
|
||||
var umbracoContextAccessor = Mock.Of<IUmbracoContextAccessor>();
|
||||
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 });
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The constructor will setup the property editor based on the attribute if one is found
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a custom value editor
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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
|
||||
/// <summary>
|
||||
/// A custom value editor to ensure that macro syntax is parsed when being persisted and formatted correctly for display in the editor
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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<string, string>());
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve references from <see cref="IDataValueEditor"/> values
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Udi> GetReferences(object value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
internal class RichTextPropertyIndexValueFactory : IPropertyIndexValueFactory
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,8 @@ namespace Umbraco.Web.Runtime
|
||||
composition.RegisterUnique<IUmbracoComponentRenderer, UmbracoComponentRenderer>();
|
||||
|
||||
composition.RegisterUnique<InternalLinkParser>();
|
||||
composition.RegisterUnique<UrlParser>();
|
||||
composition.RegisterUnique<MediaParser>();
|
||||
|
||||
// register the umbraco helper - this is Transient! very important!
|
||||
// also, if not level.Run, we cannot really use the helper (during upgrade...)
|
||||
|
||||
@@ -23,17 +23,23 @@ namespace Umbraco.Web.Templates
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
}
|
||||
|
||||
public string ParseInternalLinks(string text, bool preview)
|
||||
/// <summary>
|
||||
/// Parses the string looking for the {localLink} syntax and updates them to their correct links.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="preview"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
/// <param name="text"></param>
|
||||
/// <param name="urlProvider"></param>
|
||||
/// <returns></returns>
|
||||
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");
|
||||
|
||||
186
src/Umbraco.Web/Templates/MediaParser.cs
Normal file
186
src/Umbraco.Web/Templates/MediaParser.cs
Normal file
@@ -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(@"(<img[^>]*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";
|
||||
|
||||
/// <summary>
|
||||
/// Parses the string looking for Umbraco image tags and updates them to their up-to-date image sources.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Umbraco image tags are identified by their data-udi attributes</remarks>
|
||||
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}";
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes media urls from <img> tags where a data-udi attribute is present
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
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<string, GuidUdi>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Utility class used for templates
|
||||
/// </summary>
|
||||
[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(@"(<img[^>]*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<InternalLinkParser>().ParseInternalLinks(text);
|
||||
public static string ParseInternalLinks(string text, UrlProvider urlProvider)
|
||||
=> Current.Factory.GetInstance<InternalLinkParser>().EnsureInternalLinks(text);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
[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<UrlParser>().EnsureUrls(text);
|
||||
|
||||
[Obsolete("Use StringExtensions.CleanForXss instead")]
|
||||
public static string CleanForXss(string text, params char[] ignoreFromClean)
|
||||
{
|
||||
return text.CleanForXss(ignoreFromClean);
|
||||
}
|
||||
=> text.CleanForXss(ignoreFromClean);
|
||||
|
||||
/// <summary>
|
||||
/// Parses the string looking for Umbraco image tags and updates them to their up-to-date image sources.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Umbraco image tags are identified by their data-udi attributes</remarks>
|
||||
[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}";
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes media urls from <img> tags where a data-udi attribute is present
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
=> Current.Factory.GetInstance<MediaParser>().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<MediaParser>().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<string, GuidUdi>();
|
||||
|
||||
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<MediaParser>().FindAndPersistPastedTempImages(html, mediaParentFolder, userId);
|
||||
}
|
||||
}
|
||||
|
||||
61
src/Umbraco.Web/Templates/UrlParser.cs
Normal file
61
src/Umbraco.Web/Templates/UrlParser.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,6 +248,8 @@
|
||||
<Compile Include="Models\LinkType.cs" />
|
||||
<Compile Include="Models\TemplateQuery\OperatorFactory.cs" />
|
||||
<Compile Include="Templates\InternalLinkParser.cs" />
|
||||
<Compile Include="Templates\MediaParser.cs" />
|
||||
<Compile Include="Templates\UrlParser.cs" />
|
||||
<Compile Include="UmbracoContextFactory.cs" />
|
||||
<Compile Include="UmbracoContextReference.cs" />
|
||||
<Compile Include="ViewDataExtensions.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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user