diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index 310477fa4c..af8cdea3a3 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -292,7 +292,7 @@ stages:
Linux:
vmImage: 'ubuntu-latest'
testDb: SqlServer
- connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);'
+ connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=true'
pool:
vmImage: $(vmImage)
variables:
diff --git a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj
index fd5e7d4b83..5a2dfa69ef 100644
--- a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj
+++ b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj
@@ -10,6 +10,8 @@
+
+
diff --git a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj
index af566ab67a..3e6e57983f 100644
--- a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj
+++ b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/Umbraco.Core/Models/DeliveryApi/RichTextModel.cs b/src/Umbraco.Core/Models/DeliveryApi/RichTextModel.cs
new file mode 100644
index 0000000000..2af7570183
--- /dev/null
+++ b/src/Umbraco.Core/Models/DeliveryApi/RichTextModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Core.Models.DeliveryApi;
+
+public class RichTextModel
+{
+ public required string Markup { get; set; }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
index f863c96151..d0ab92223b 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
@@ -204,6 +204,13 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe
IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
var entityType = GetEntityType(propertyType);
+
+ if (entityType == "content")
+ {
+ // TODO Why do MNTP config saves "content" and not "document"?
+ entityType = Constants.UdiEntityType.Document;
+ }
+
GuidUdi[] entityTypeUdis = udis.Where(udi => udi.EntityType == entityType).OfType().ToArray();
return entityType switch
{
diff --git a/src/Umbraco.Infrastructure/Migrations/ExecutedMigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/ExecutedMigrationPlan.cs
index 065a8e049f..09d726fd6a 100644
--- a/src/Umbraco.Infrastructure/Migrations/ExecutedMigrationPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/ExecutedMigrationPlan.cs
@@ -24,6 +24,7 @@ public class ExecutedMigrationPlan
FinalState = finalState ?? throw new ArgumentNullException(nameof(finalState));
Successful = successful;
CompletedTransitions = completedTransitions;
+ ExecutedMigrationContexts = Array.Empty();
}
public ExecutedMigrationPlan()
@@ -59,4 +60,7 @@ public class ExecutedMigrationPlan
/// A collection of all the succeeded transition.
///
public required IReadOnlyList CompletedTransitions { get; init; }
+
+ [Obsolete("This will be removed in the V13, and replaced with UmbracoPlanExecutedNotification")]
+ internal IReadOnlyList ExecutedMigrationContexts { get; init; } = Array.Empty();
}
diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs
index 0001b3381d..5e0766755a 100644
--- a/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs
+++ b/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs
@@ -37,4 +37,11 @@ public interface IMigrationContext
/// Gets or sets a value indicating whether an expression is being built.
///
bool BuildingExpression { get; set; }
+
+ ///
+ /// Adds a post-migration.
+ ///
+ [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase")]
+ void AddPostMigration()
+ where TMigration : MigrationBase;
}
diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs b/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs
index 4b1900c27c..c07adbec90 100644
--- a/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs
+++ b/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs
@@ -8,6 +8,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations;
///
internal class MigrationContext : IMigrationContext
{
+ private readonly List _postMigrations = new();
+
///
/// Initializes a new instance of the class.
///
@@ -18,6 +20,10 @@ internal class MigrationContext : IMigrationContext
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
+ // this is only internally exposed
+ [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase")]
+ internal IReadOnlyList PostMigrations => _postMigrations;
+
///
public ILogger Logger { get; }
@@ -34,4 +40,13 @@ internal class MigrationContext : IMigrationContext
///
public bool BuildingExpression { get; set; }
+
+
+ ///
+ [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase, and a UmbracoPlanExecutedNotification.")]
+ public void AddPostMigration()
+ where TMigration : MigrationBase =>
+
+ // just adding - will be de-duplicated when executing
+ _postMigrations.Add(typeof(TMigration));
}
diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
index c271088626..faea87bd68 100644
--- a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
+++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
@@ -99,6 +99,8 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
ExecutedMigrationPlan result = RunMigrationPlan(plan, fromState);
+ HandlePostMigrations(result);
+
// If any completed migration requires us to rebuild cache we'll do that.
if (_rebuildCache)
{
@@ -108,6 +110,33 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
return result;
}
+ [Obsolete]
+ private void HandlePostMigrations(ExecutedMigrationPlan result)
+ {
+ // prepare and de-duplicate post-migrations, only keeping the 1st occurence
+ var executedTypes = new HashSet();
+
+ foreach (var executedMigrationContext in result.ExecutedMigrationContexts)
+ {
+ if (executedMigrationContext is MigrationContext migrationContext)
+ {
+ foreach (Type migrationContextPostMigration in migrationContext.PostMigrations)
+ {
+ if (executedTypes.Contains(migrationContextPostMigration))
+ {
+ continue;
+ }
+
+ _logger.LogInformation($"PostMigration: {migrationContextPostMigration.FullName}.");
+ MigrationBase postMigration = _migrationBuilder.Build(migrationContextPostMigration, executedMigrationContext);
+ postMigration.Run();
+
+ executedTypes.Add(migrationContextPostMigration);
+ }
+ }
+ }
+ }
+
private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromState)
{
_logger.LogInformation("Starting '{MigrationName}'...", plan.Name);
@@ -122,6 +151,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
List completedTransitions = new();
+ var executedMigrationContexts = new List();
while (transition is not null)
{
_logger.LogInformation("Execute {MigrationType}", transition.MigrationType.Name);
@@ -130,11 +160,11 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
{
if (transition.MigrationType.IsAssignableTo(typeof(UnscopedMigrationBase)))
{
- RunUnscopedMigration(transition.MigrationType, plan);
+ executedMigrationContexts.Add(RunUnscopedMigration(transition.MigrationType, plan));
}
else
{
- RunScopedMigration(transition.MigrationType, plan);
+ executedMigrationContexts.Add(RunScopedMigration(transition.MigrationType, plan));
}
}
catch (Exception exception)
@@ -149,6 +179,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
FinalState = transition.SourceState,
CompletedTransitions = completedTransitions,
Plan = plan,
+ ExecutedMigrationContexts = executedMigrationContexts
};
}
@@ -197,18 +228,21 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
FinalState = finalState,
CompletedTransitions = completedTransitions,
Plan = plan,
+ ExecutedMigrationContexts = executedMigrationContexts
};
}
- private void RunUnscopedMigration(Type migrationType, MigrationPlan plan)
+ private MigrationContext RunUnscopedMigration(Type migrationType, MigrationPlan plan)
{
using IUmbracoDatabase database = _databaseFactory.CreateDatabase();
var context = new MigrationContext(plan, database, _loggerFactory.CreateLogger());
RunMigration(migrationType, context);
+
+ return context;
}
- private void RunScopedMigration(Type migrationType, MigrationPlan plan)
+ private MigrationContext RunScopedMigration(Type migrationType, MigrationPlan plan)
{
// We want to suppress scope (service, etc...) notifications during a migration plan
// execution. This is because if a package that doesn't have their migration plan
@@ -225,6 +259,8 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
RunMigration(migrationType, context);
scope.Complete();
+
+ return context;
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_0_0/ResetCache.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_0_0/ResetCache.cs
index ce105bf6b8..a3ec96ad7e 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_0_0/ResetCache.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_0_0/ResetCache.cs
@@ -22,6 +22,11 @@ public class ResetCache : MigrationBase
private void DeleteAllFilesInFolder(string path)
{
+ if (Directory.Exists(path) == false)
+ {
+ return;
+ }
+
var directoryInfo = new DirectoryInfo(path);
foreach (FileInfo file in directoryInfo.GetFiles())
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs
index 68d15aa99f..9d8010ab5f 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs
@@ -82,13 +82,16 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel
public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType)
=> _deliveryApiSettings.RichTextOutputAsJson
? typeof(IRichTextElement)
- : typeof(string);
+ : typeof(RichTextModel);
public object? ConvertIntermediateToDeliveryApiObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)
{
if (_deliveryApiSettings.RichTextOutputAsJson is false)
{
- return Convert(inter, preview) ?? string.Empty;
+ return new RichTextModel
+ {
+ Markup = Convert(inter, preview, false) ?? string.Empty
+ };
}
var sourceString = inter?.ToString();
@@ -126,7 +129,7 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel
}
}
- private string? Convert(object? source, bool preview)
+ private string? Convert(object? source, bool preview, bool handleMacros = true)
{
if (source == null)
{
@@ -141,7 +144,10 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel
sourceString = _imageSourceParser.EnsureImageSources(sourceString);
// ensure string is parsed for macros and macros are executed correctly
- sourceString = RenderRteMacros(sourceString, preview);
+ if (handleMacros)
+ {
+ sourceString = RenderRteMacros(sourceString, preview);
+ }
// find and remove the rel attributes used in the Umbraco UI from img tags
var doc = new HtmlDocument();
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs
index 80e175bf81..276df9a223 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs
@@ -93,7 +93,9 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest
}
[Test]
- public void MultiNodeTreePickerValueConverter_RendersContentProperties()
+ [TestCase(Constants.UdiEntityType.Document)]
+ [TestCase("content")]
+ public void MultiNodeTreePickerValueConverter_RendersContentProperties(string entityType)
{
var content = new Mock();
@@ -112,7 +114,7 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest
.Setup(pcc => pcc.GetById(key))
.Returns(content.Object);
- var publishedDataType = MultiNodePickerPublishedDataType(false, Constants.UdiEntityType.Document);
+ var publishedDataType = MultiNodePickerPublishedDataType(false, entityType);
var publishedPropertyType = new Mock();
publishedPropertyType.SetupGet(p => p.DataType).Returns(publishedDataType);
diff --git a/version.json b/version.json
index 1af6ab23c5..e22da7303c 100644
--- a/version.json
+++ b/version.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "12.0.0-rc",
+ "version": "12.0.0-rc1",
"assemblyVersion": {
"precision": "build"
},