Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<PackageVersion Include="JsonPatch.Net" Version="3.3.0" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="MailKit" Version="4.14.1" />
<PackageVersion Include="Markdig" Version="0.44.0" />
<PackageVersion Include="Markdown" Version="2.2.1" />
<PackageVersion Include="MessagePack" Version="3.1.4" />
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" />
Expand Down
8 changes: 8 additions & 0 deletions NOTICES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,14 @@ Copyright: 2013-2024 .NET Foundation and Contributors

---

Markdig: A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET

URL: https://github.com/xoofx/markdig
License: BSD-2-Clause license
Copyright: 2018+, Alexandre Mutel. All rights reserved.

---

Markdown: A library for parsing and compiling Markdown

URL: https://github.com/hey-red/Markdown
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Composing/TypeFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class TypeFinder : ITypeFinder
"DataAnnotationsExtensions,", "DataAnnotationsExtensions.", "Dynamic,", "Examine,", "Examine.",
"HtmlAgilityPack,", "HtmlAgilityPack.", "HtmlDiff,", "ICSharpCode.", "Iesi.Collections,", // used by NHibernate
"JetBrains.Annotations,", "LightInject.", // DI
"LightInject,", "Lucene.", "Markdown,", "Microsoft.", "MiniProfiler,", "Moq,", "MySql.", "NHibernate,",
"LightInject,", "Lucene.", "Markdig,", "Markdown,", "Microsoft.", "MiniProfiler,", "Moq,", "MySql.", "NHibernate,",
"NHibernate.", "Newtonsoft.", "NPoco,", "NuGet.", "RouteDebugger,", "Semver.", "Serilog.", "Serilog,",
"ServiceStack.", "SqlCE4Umbraco,", "Superpower,", // used by Serilog
"System.", "TidyNet,", "TidyNet.", "WebDriver,", "itextsharp,", "mscorlib,", "NUnit,", "NUnit.", "NUnit3.",
Expand Down
14 changes: 14 additions & 0 deletions src/Umbraco.Core/Strings/IMarkdownToHtmlConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Umbraco.Cms.Core.Strings;

/// <summary>
/// Defines a service that converts Markdown-formatted text to HTML.
/// </summary>
public interface IMarkdownToHtmlConverter
{
/// <summary>
/// Converts the specified Markdown-formatted text to an HTML-encoded string.
/// </summary>
/// <param name="markdown">The input string containing Markdown syntax to be converted.</param>
/// <returns>A string containing the HTML representation of the input Markdown.</returns>
public string ToHtml(string markdown);
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builde
.Remove<SimpleRichTextValueConverter>();

// register *all* checks, except those marked [HideFromTypeFinder] of course
builder.Services.AddSingleton<IMarkdownToHtmlConverter, MarkdownToHtmlConverter>();
builder.Services.AddSingleton<Core.HealthChecks.NotificationMethods.IMarkdownToHtmlConverter, MarkdownToHtmlConverter>();

builder.Services.AddSingleton<IContentLastChanceFinder, ContentFinderByConfigured404>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Cms.Infrastructure.Services;
using Umbraco.Cms.Infrastructure.Services.Implement;
using Umbraco.Cms.Infrastructure.Strings;
using Umbraco.Cms.Infrastructure.Telemetry.Providers;
using Umbraco.Cms.Infrastructure.Templates.PartialViews;
using Umbraco.Extensions;
Expand Down Expand Up @@ -87,6 +88,11 @@ internal static IUmbracoBuilder AddServices(this IUmbracoBuilder builder)
builder.Services.AddUnique<IMediaSearchService, MediaSearchService>();
builder.Services.AddUnique<IDistributedJobService, DistributedJobService>();

#pragma warning disable CS0618 // Type or member is obsolete
// TODO (V18): Replace this with MarkdigMarkdownToHtmlConverter as the default implementation.
builder.Services.AddUnique<IMarkdownToHtmlConverter, HeyRedMarkdownToHtmlConverter>();
#pragma warning restore CS0618 // Type or member is obsolete

return builder;
}

Expand Down
16 changes: 13 additions & 3 deletions src/Umbraco.Infrastructure/HealthChecks/MarkdownToHtmlConverter.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
using HeyRed.MarkdownSharp;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.HealthChecks;
using Umbraco.Cms.Core.HealthChecks.NotificationMethods;

namespace Umbraco.Cms.Infrastructure.HealthChecks;

public class MarkdownToHtmlConverter : IMarkdownToHtmlConverter
{
private readonly Core.Strings.IMarkdownToHtmlConverter _markdownToHtmlConverter;

public MarkdownToHtmlConverter(Core.Strings.IMarkdownToHtmlConverter markdownToHtmlConverter) => _markdownToHtmlConverter = markdownToHtmlConverter;

[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public MarkdownToHtmlConverter()
: this(StaticServiceProvider.Instance.GetRequiredService<Core.Strings.IMarkdownToHtmlConverter>())
{
}

public string ToHtml(HealthCheckResults results, HealthCheckNotificationVerbosity verbosity)
{
var mark = new Markdown();
var html = mark.Transform(results.ResultsAsMarkDown(verbosity));
var html = _markdownToHtmlConverter.ToHtml(results.ResultsAsMarkDown(verbosity));
html = ApplyHtmlHighlighting(html);
return html;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using HeyRed.MarkdownSharp;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors.DeliveryApi;
using Umbraco.Cms.Core.Strings;
Expand All @@ -15,11 +16,22 @@ public class MarkdownEditorValueConverter : PropertyValueConverterBase, IDeliver
{
private readonly HtmlLocalLinkParser _localLinkParser;
private readonly HtmlUrlParser _urlParser;
private readonly IMarkdownToHtmlConverter _markdownToHtmlConverter;

public MarkdownEditorValueConverter(HtmlLocalLinkParser localLinkParser, HtmlUrlParser urlParser)
public MarkdownEditorValueConverter(HtmlLocalLinkParser localLinkParser, HtmlUrlParser urlParser, IMarkdownToHtmlConverter markdownToHtmlConverter)
{
_localLinkParser = localLinkParser;
_urlParser = urlParser;
_markdownToHtmlConverter = markdownToHtmlConverter;
}

[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public MarkdownEditorValueConverter(HtmlLocalLinkParser localLinkParser, HtmlUrlParser urlParser)
: this(
localLinkParser,
urlParser,
StaticServiceProvider.Instance.GetRequiredService<IMarkdownToHtmlConverter>())
{
}

public override bool IsConverter(IPublishedPropertyType propertyType)
Expand Down Expand Up @@ -49,10 +61,15 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType

public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)
{
// convert markup to HTML for frontend rendering.
// source should come from ConvertSource and be a string (or null) already
var mark = new Markdown();
return new HtmlEncodedString(inter == null ? string.Empty : mark.Transform((string)inter));
// Convert markup to HTML for frontend rendering.
// Source should come from ConvertSource and be a string (or null) already.
if (inter is null)
{
return new HtmlEncodedString(string.Empty);
}

var htmlString = _markdownToHtmlConverter.ToHtml((string)inter);
return new HtmlEncodedString(htmlString);
}

public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using HeyRed.MarkdownSharp;
using Umbraco.Cms.Core.Strings;

namespace Umbraco.Cms.Infrastructure.Strings;

// TODO (V19): Remove this class along with the HeyRed.MarkdownSharp library entirely (remove reference from Directory.props and .csproj, and remove from NOTICES.txt).

/// <summary>
/// Implements a service that converts Markdown-formatted text to HTML using the HeyRed.MarkdownSharp library.
/// </summary>
[Obsolete("Uses the deprecated HeyRed.MarkdownSharp library which will continue to be provided for the lifetime of Umbraco 17 as the default implementation of IMarkdownToHtmlConverter. The default will be changed to MarkdigMarkdownToHtmlConverter for Umbraco 18. Scheduled for removal along with the HeyRed.MarkdownSharp library in Umbraco 19.")]
public class HeyRedMarkdownToHtmlConverter : IMarkdownToHtmlConverter
{
private static readonly Markdown _markdownConverter = new();

/// <inheritdoc/>
public string ToHtml(string markdown) => _markdownConverter.Transform(markdown);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Markdig;
using Umbraco.Cms.Core.Strings;

namespace Umbraco.Cms.Infrastructure.Strings;

/// <summary>
/// Implements a service that converts Markdown-formatted text to HTML using the Markdig library.
/// </summary>
public class MarkdigMarkdownToHtmlConverter : IMarkdownToHtmlConverter
{
private static readonly MarkdownPipeline _markdownPipeline = new MarkdownPipelineBuilder().Build();

/// <inheritdoc/>
public string ToHtml(string markdown) => Markdown.ToHtml(markdown, _markdownPipeline);
}
3 changes: 2 additions & 1 deletion src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Umbraco.Cms.Infrastructure</PackageId>
<Title>Umbraco CMS - Infrastructure</Title>
Expand Down Expand Up @@ -39,6 +39,7 @@
<PackageReference Include="Examine.Core" />
<PackageReference Include="HtmlAgilityPack" />
<PackageReference Include="MailKit" />
<PackageReference Include="Markdig" />
<PackageReference Include="Markdown" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Templates;
using Umbraco.Cms.Infrastructure.Strings;

namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.DeliveryApi;

Expand All @@ -25,7 +26,9 @@ public void MarkdownEditorValueConverter_ConvertsValueToMarkdownString(object in
{
var linkParser = new HtmlLocalLinkParser(Mock.Of<IPublishedUrlProvider>());
var urlParser = new HtmlUrlParser(Mock.Of<IOptionsMonitor<ContentSettings>>(), Mock.Of<ILogger<HtmlUrlParser>>(), Mock.Of<IProfilingLogger>(), Mock.Of<IIOHelper>());
var valueConverter = new MarkdownEditorValueConverter(linkParser, urlParser);
#pragma warning disable CS0618 // Type or member is obsolete
var valueConverter = new MarkdownEditorValueConverter(linkParser, urlParser, new HeyRedMarkdownToHtmlConverter());
#pragma warning restore CS0618 // Type or member is obsolete

Assert.AreEqual(typeof(string), valueConverter.GetDeliveryApiPropertyValueType(Mock.Of<IPublishedPropertyType>()));
var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of<IPublishedElement>(), Mock.Of<IPublishedPropertyType>(), PropertyCacheLevel.Element, inter, false, false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using NUnit.Framework;
using Umbraco.Cms.Core.HealthChecks;
using Umbraco.Cms.Infrastructure.HealthChecks;
using Umbraco.Cms.Infrastructure.Strings;

namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks;

[TestFixture]
public class MarkdownToHtmlConverterTests
{
[HealthCheck("CFD6FC34-59C9-4402-B55F-C8BC96B628A1", "Stub check")]
public abstract class StubHealthCheck : HealthCheck
{
private readonly string _message;
private readonly StatusResultType _resultType;

protected StubHealthCheck(StatusResultType resultType, string message)
{
_resultType = resultType;
_message = message;
}

public override HealthCheckStatus ExecuteAction(HealthCheckAction action) =>
throw new NotImplementedException();

public override Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() =>
Task.FromResult<IEnumerable<HealthCheckStatus>>([new(_message) { ResultType = _resultType }]);
}

[HealthCheck("CFD6FC34-59C9-4402-B55F-C8BC96B628A1", "Success Check")]
public class SuccessHealthCheck : StubHealthCheck
{
public SuccessHealthCheck()
: base(StatusResultType.Success, "Check passed")
{
}
}

[HealthCheck("CFD6FC34-59C9-4402-B55F-C8BC96B628A2", "Warning Check")]
public class WarningHealthCheck : StubHealthCheck
{
public WarningHealthCheck()
: base(StatusResultType.Warning, "Check has warnings")
{
}
}

[HealthCheck("CFD6FC34-59C9-4402-B55F-C8BC96B628A3", "Error Check")]
public class ErrorHealthCheck : StubHealthCheck
{
public ErrorHealthCheck()
: base(StatusResultType.Error, "Check failed")
{
}
}

#pragma warning disable CS0618 // Type or member is obsolete
private static MarkdownToHtmlConverter CreateConverter() =>
new(new HeyRedMarkdownToHtmlConverter());
#pragma warning restore CS0618 // Type or member is obsolete

[Test]
public async Task ToHtml_WithSuccessResult_AppliesGreenHighlighting()
{
var checks = new List<HealthCheck> { new SuccessHealthCheck() };
var results = await HealthCheckResults.Create(checks);
var converter = CreateConverter();

var html = converter.ToHtml(results, HealthCheckNotificationVerbosity.Summary);

Assert.That(html, Does.Contain("<span style=\"color: #5cb85c\">Success</span>"));
}

[Test]
public async Task ToHtml_WithWarningResult_AppliesOrangeHighlighting()
{
var checks = new List<HealthCheck> { new WarningHealthCheck() };
var results = await HealthCheckResults.Create(checks);
var converter = CreateConverter();

var html = converter.ToHtml(results, HealthCheckNotificationVerbosity.Summary);

Assert.That(html, Does.Contain("<span style=\"color: #f0ad4e\">Warning</span>"));
}

[Test]
public async Task ToHtml_WithErrorResult_AppliesRedHighlighting()
{
var checks = new List<HealthCheck> { new ErrorHealthCheck() };
var results = await HealthCheckResults.Create(checks);
var converter = CreateConverter();

var html = converter.ToHtml(results, HealthCheckNotificationVerbosity.Summary);

Assert.That(html, Does.Contain("<span style=\"color: #d9534f\">Error</span>"));
}

[Test]
public async Task ToHtml_WithMixedResults_AppliesCorrectHighlightingForEachStatus()
{
var checks = new List<HealthCheck>
{
new SuccessHealthCheck(),
new WarningHealthCheck(),
new ErrorHealthCheck(),
};
var results = await HealthCheckResults.Create(checks);
var converter = CreateConverter();

var html = converter.ToHtml(results, HealthCheckNotificationVerbosity.Summary);

Assert.Multiple(() =>
{
Assert.That(html, Does.Contain("<span style=\"color: #5cb85c\">Success</span>"), "Success should have green highlighting");
Assert.That(html, Does.Contain("<span style=\"color: #f0ad4e\">Warning</span>"), "Warning should have orange highlighting");
Assert.That(html, Does.Contain("<span style=\"color: #d9534f\">Error</span>"), "Error should have red highlighting");
});
}

[Test]
public async Task ToHtml_WithDetailedVerbosity_IncludesMessageInOutput()
{
var checks = new List<HealthCheck> { new SuccessHealthCheck() };
var results = await HealthCheckResults.Create(checks);
var converter = CreateConverter();

var html = converter.ToHtml(results, HealthCheckNotificationVerbosity.Detailed);

Assert.That(html, Does.Contain("Check passed"));
}

[Test]
public async Task ToHtml_WithSummaryVerbosity_DoesNotIncludeSuccessMessageInOutput()
{
var checks = new List<HealthCheck> { new SuccessHealthCheck() };
var results = await HealthCheckResults.Create(checks);
var converter = CreateConverter();

var html = converter.ToHtml(results, HealthCheckNotificationVerbosity.Summary);

Assert.That(html, Does.Not.Contain("Check passed"));
}
}
Loading
Loading