Merge remote-tracking branch 'origin/v10/dev' into v11/merge_v10

# Conflicts:
#	src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs
#	tests/Umbraco.Tests.AcceptanceTest/package-lock.json
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/macro.spec.ts
This commit is contained in:
nikolajlauridsen
2022-10-20 09:37:14 +02:00
19 changed files with 286 additions and 22 deletions

View File

@@ -23,6 +23,7 @@ using Umbraco.Cms.Infrastructure.Services;
using Umbraco.Cms.Infrastructure.Services.Implement;
using Umbraco.Cms.Infrastructure.Telemetry.Providers;
using Umbraco.Cms.Infrastructure.Templates;
using Umbraco.Cms.Infrastructure.Templates.PartialViews;
using Umbraco.Extensions;
using CacheInstructionService = Umbraco.Cms.Core.Services.Implement.CacheInstructionService;
@@ -58,6 +59,7 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddUnique<IUserDataService, SystemInformationTelemetryProvider>();
builder.Services.AddTransient<IUsageInformationService, UsageInformationService>();
builder.Services.AddSingleton<IEditorConfigurationParser, EditorConfigurationParser>();
builder.Services.AddTransient<IPartialViewPopulator, PartialViewPopulator>();
return builder;
}

View File

@@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Semver;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.Common;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_0_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_2_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_3_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_1;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_1_0;
@@ -293,5 +294,8 @@ public class UmbracoPlan : MigrationPlan
// TO 10.2.0
To<AddUserGroup2LanguageTable>("{D0B3D29D-F4D5-43E3-BA67-9D49256F3266}");
To<AddHasAccessToAllLanguagesColumn>("{79D8217B-5920-4C0E-8E9A-3CF8FA021882}");
// To 10.3.0
To<AddBlockGridPartialViews>("{56833770-3B7E-4FD5-A3B6-3416A26A7A3F}");
}
}

View File

@@ -0,0 +1,31 @@
using Umbraco.Cms.Infrastructure.Templates.PartialViews;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_3_0;
public class AddBlockGridPartialViews : MigrationBase
{
private readonly IPartialViewPopulator _partialViewPopulator;
private const string FolderPath = "/Views/Partials/blockgrid";
private static readonly string[] _filesToAdd =
{
"areas.cshtml",
"default.cshtml",
"items.cshtml",
};
public AddBlockGridPartialViews(IMigrationContext context, IPartialViewPopulator partialViewPopulator) : base(context)
=> _partialViewPopulator = partialViewPopulator;
protected override void Migrate()
{
var embeddedBasePath = _partialViewPopulator.CoreEmbeddedPath + ".BlockGrid";
foreach (var fileName in _filesToAdd)
{
_partialViewPopulator.CopyPartialViewIfNotExists(
_partialViewPopulator.GetCoreAssembly(),
$"{embeddedBasePath}.{fileName}",
$"{FolderPath}/{fileName}");
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Reflection;
namespace Umbraco.Cms.Infrastructure.Templates.PartialViews;
/// <summary>
/// Populates the Partial View file system using other sources, such as RCL.
/// </summary>
public interface IPartialViewPopulator
{
/// <summary>
/// Copies a partial view from the assembly path within the provided assembly, to the file system path. But only if it does not exist yet.
/// </summary>
/// <param name="assembly">The assembly to look for embedded resources in.</param>
/// <param name="embeddedPath">Path to resource as assembly path I.E Umbraco.Cms.Core.EmbeddedResources.</param>
/// <param name="fileSystemPath">The partial view filesystem path to copy the file to, I.E. /Views/Partials/blockgrid.</param>
void CopyPartialViewIfNotExists(Assembly assembly, string embeddedPath, string fileSystemPath);
Assembly GetCoreAssembly();
string CoreEmbeddedPath { get; }
}

View File

@@ -0,0 +1,51 @@
using System.Reflection;
using System.Text;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Infrastructure.Templates.PartialViews;
/// <inheritdoc />
internal sealed class PartialViewPopulator : IPartialViewPopulator
{
private readonly IFileService _fileService;
public PartialViewPopulator(IFileService fileService)
{
_fileService = fileService;
}
public Assembly GetCoreAssembly() => typeof(Constants).Assembly;
public string CoreEmbeddedPath => "Umbraco.Cms.Core.EmbeddedResources";
/// <inheritdoc/>
public void CopyPartialViewIfNotExists(Assembly assembly, string embeddedPath, string fileSystemPath)
{
Stream? content = assembly.GetManifestResourceStream(embeddedPath);
if (content is not null)
{
// We have to ensure that this is idempotent, so only save the view if it does not already exist
// We don't want to overwrite any changes made.
IPartialView? existingView = _fileService.GetPartialView(fileSystemPath);
if (existingView is null)
{
var view = new PartialView(PartialViewType.PartialView, fileSystemPath)
{
Content = GetTextFromStream(content)
};
_fileService.SavePartialView(view);
}
}
}
private string GetTextFromStream(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(stream, Encoding.UTF8);
return streamReader.ReadToEnd();
}
}

View File

@@ -5,23 +5,31 @@ using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Templates.PartialViews;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Controllers;
internal class BlockGridSampleHelper
// Unfortunately this has to be public to be injected into a controller
public sealed class BlockGridSampleHelper
{
private const string ContainerName = "Umbraco Block Grid Demo";
private readonly IContentTypeService _contentTypeService;
private readonly IDataTypeService _dataTypeService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IPartialViewPopulator _partialViewPopulator;
public BlockGridSampleHelper(IContentTypeService contentTypeService, IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
public BlockGridSampleHelper(
IContentTypeService contentTypeService,
IDataTypeService dataTypeService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IPartialViewPopulator partialViewPopulator)
{
_contentTypeService = contentTypeService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_partialViewPopulator = partialViewPopulator;
_dataTypeService = dataTypeService;
}
@@ -35,7 +43,7 @@ internal class BlockGridSampleHelper
/// <param name="createElement">The function that will perform the actual element creation</param>
/// <param name="errorMessage">If an error occurs, this message will describe that error</param>
/// <returns>A mapping table between element aliases and the created element UDIs, or null if an error occurs</returns>
public Dictionary<string, Udi>? CreateSampleElements(Func<DocumentTypeSave, ActionResult<IContentType?>> createElement, out string errorMessage)
internal Dictionary<string, Udi>? CreateSampleElements(Func<DocumentTypeSave, ActionResult<IContentType?>> createElement, out string errorMessage)
{
errorMessage = string.Empty;
@@ -161,6 +169,26 @@ internal class BlockGridSampleHelper
return elementUdisByAlias;
}
internal void CreateSamplePartialViews()
{
var embeddedBasePath = $"{_partialViewPopulator.CoreEmbeddedPath}.BlockGrid.Components";
var fileSystemBasePath = "/Views/partials/blockgrid/Components";
var filesToMove = new[]
{
"umbBlockGridDemoHeadlineBlock.cshtml",
"umbBlockGridDemoImageBlock.cshtml",
"umbBlockGridDemoRichTextBlock.cshtml",
"umbBlockGridDemoTwoColumnLayoutBlock.cshtml",
};
foreach (var fileName in filesToMove)
{
var embeddedPath = $"{embeddedBasePath}.{fileName}";
var fileSystemPath = $"{fileSystemBasePath}/{fileName}";
_partialViewPopulator.CopyPartialViewIfNotExists(_partialViewPopulator.GetCoreAssembly(), embeddedPath, fileSystemPath);
}
}
private EntityContainer? GetOrCreateContainer()
{
var userId = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1;

View File

@@ -5,6 +5,7 @@ using System.Xml.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Dictionary;
@@ -21,6 +22,7 @@ using Umbraco.Cms.Infrastructure.Packaging;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
using ContentType = Umbraco.Cms.Core.Models.ContentType;
@@ -43,6 +45,7 @@ public class ContentTypeController : ContentTypeControllerBase<IContentType>
private readonly ILocalizedTextService _localizedTextService;
private readonly ILogger<ContentTypeController> _logger;
private readonly PackageDataInstallation _packageDataInstallation;
private readonly BlockGridSampleHelper _blockGridSampleHelper;
private readonly PropertyEditorCollection _propertyEditors;
// TODO: Split this controller apart so that authz is consistent, currently we need to authz each action individually.
@@ -52,6 +55,7 @@ public class ContentTypeController : ContentTypeControllerBase<IContentType>
private readonly IShortStringHelper _shortStringHelper;
private readonly IUmbracoMapper _umbracoMapper;
[Obsolete("Use constructor that takes BlockGridSampleHelper as a parameter")]
public ContentTypeController(
ICultureDictionary cultureDictionary,
IContentTypeService contentTypeService,
@@ -71,6 +75,51 @@ public class ContentTypeController : ContentTypeControllerBase<IContentType>
IHostingEnvironment hostingEnvironment,
EditorValidatorCollection editorValidatorCollection,
PackageDataInstallation packageDataInstallation)
: this(
cultureDictionary,
contentTypeService,
mediaTypeService,
memberTypeService,
umbracoMapper,
localizedTextService,
serializer,
propertyEditors,
backofficeSecurityAccessor,
dataTypeService,
shortStringHelper,
fileService,
logger,
contentService,
contentTypeBaseServiceProvider,
hostingEnvironment,
editorValidatorCollection,
packageDataInstallation,
StaticServiceProvider.Instance.GetRequiredService<BlockGridSampleHelper>()
)
{
}
[ActivatorUtilitiesConstructor]
public ContentTypeController(
ICultureDictionary cultureDictionary,
IContentTypeService contentTypeService,
IMediaTypeService mediaTypeService,
IMemberTypeService memberTypeService,
IUmbracoMapper umbracoMapper,
ILocalizedTextService localizedTextService,
IEntityXmlSerializer serializer,
PropertyEditorCollection propertyEditors,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IDataTypeService dataTypeService,
IShortStringHelper shortStringHelper,
IFileService fileService,
ILogger<ContentTypeController> logger,
IContentService contentService,
IContentTypeBaseServiceProvider contentTypeBaseServiceProvider,
IHostingEnvironment hostingEnvironment,
EditorValidatorCollection editorValidatorCollection,
PackageDataInstallation packageDataInstallation,
BlockGridSampleHelper blockGridSampleHelper)
: base(
cultureDictionary,
editorValidatorCollection,
@@ -94,6 +143,7 @@ public class ContentTypeController : ContentTypeControllerBase<IContentType>
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
_hostingEnvironment = hostingEnvironment;
_packageDataInstallation = packageDataInstallation;
_blockGridSampleHelper = blockGridSampleHelper;
}
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
@@ -654,14 +704,16 @@ public class ContentTypeController : ContentTypeControllerBase<IContentType>
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
public ActionResult PostCreateBlockGridSample()
{
var sampleHelper = new BlockGridSampleHelper(_contentTypeService, _dataTypeService, _backofficeSecurityAccessor);
Dictionary<string, Udi>? elementUdisByAlias = sampleHelper.CreateSampleElements(
Dictionary<string, Udi>? elementUdisByAlias = _blockGridSampleHelper.CreateSampleElements(
documentTypeSave => PerformPostSave<DocumentTypeDisplay, DocumentTypeSave, PropertyTypeBasic>(
documentTypeSave,
i => _contentTypeService.Get(i),
type => _contentTypeService.Save(type)),
out string errorMessage);
// Create the partial views if they don't exist
_blockGridSampleHelper.CreateSamplePartialViews();
return elementUdisByAlias != null ? Ok(elementUdisByAlias) : ValidationProblem(errorMessage);
}
}

View File

@@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.DependencyInjection;
using Umbraco.Cms.Infrastructure.Examine.DependencyInjection;
using Umbraco.Cms.Infrastructure.Templates.PartialViews;
using Umbraco.Cms.Infrastructure.WebAssets;
using Umbraco.Cms.Web.BackOffice.Controllers;
using Umbraco.Cms.Web.BackOffice.Filters;
@@ -116,6 +117,7 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddUnique<IIconService, IconService>();
builder.Services.AddUnique<IConflictingRouteService, ConflictingRouteService>();
builder.Services.AddSingleton<UnhandledExceptionLoggerMiddleware>();
builder.Services.AddTransient<BlockGridSampleHelper>();
return builder;
}

View File

@@ -0,0 +1,19 @@
@using Umbraco.Extensions
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem>
@{
if (Model?.Areas.Any() != true) { return; }
}
<div class="umb-block-grid__area-container"
style="--umb-block-grid--area-grid-columns: @(Model.AreaGridColumns?.ToString() ?? Model.GridColumns?.ToString() ?? "12");">
@foreach (var area in Model.Areas)
{
<div class="umb-block-grid__area"
data-area-col-span="@area.ColumnSpan"
data-area-row-span="@area.RowSpan"
data-area-alias="@area.Alias"
style="--umb-block-grid--grid-columns: @area.ColumnSpan;--umb-block-grid--area-column-span: @area.ColumnSpan; --umb-block-grid--area-row-span: @area.RowSpan;">
@await Html.GetBlockGridItemsHtmlAsync(area)
</div>
}
</div>

View File

@@ -0,0 +1,11 @@
@using Umbraco.Extensions
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridModel>
@{
if (Model?.Any() != true) { return; }
}
<div class="umb-block-grid"
data-grid-columns="@(Model.GridColumns?.ToString() ?? "12");"
style="--umb-block-grid--grid-columns: @(Model.GridColumns?.ToString() ?? "12");">
@await Html.GetBlockGridItemsHtmlAsync(Model)
</div>

View File

@@ -0,0 +1,39 @@
@using Umbraco.Cms.Core.Models.Blocks
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<IEnumerable<BlockGridItem>>
@{
if (Model?.Any() != true) { return; }
}
<div class="umb-block-grid__layout-container">
@foreach (var item in Model)
{
bool attrForceLeft = item.ForceLeft;
bool attrForceRight = item.ForceRight;
<div
class="umb-block-grid__layout-item"
data-content-element-type-alias="@item.Content.ContentType.Alias"
data-content-element-type-key="@item.Content.ContentType.Key"
data-element-udi="@item.ContentUdi"
data-col-span="@item.ColumnSpan"
data-row-span="@item.RowSpan"
@(attrForceLeft ? "data-force-left" : null)
@(attrForceRight ? "data-force-right" : null)
style=" --umb-block-grid--item-column-span: @item.ColumnSpan; --umb-block-grid--item-row-span: @item.RowSpan; ">
@{
var partialViewName = "blockgrid/Components/" + item.Content.ContentType.Alias;
try
{
@await Html.PartialAsync(partialViewName, item)
}
catch (InvalidOperationException)
{
<p>
<strong>Could not render component of type: @(item.Content.ContentType.Alias)</strong>
<br/>
This likely happened because the partial view <em>@partialViewName</em> could not be found.
</p>
}
}
</div>
}
</div>

View File

@@ -29,6 +29,10 @@
<Link>UmbracoProject\Views\Partials\grid\%(RecursiveDir)%(Filename)%(Extension)</Link>
<PackagePath>UmbracoProject\Views\Partials\grid</PackagePath>
</Content>
<Content Include="..\src\Umbraco.Web.UI\Views\Partials\blockgrid\**">
<Link>UmbracoProject\Views\Partials\blockgrid\%(RecursiveDir)%(Filename)%(Extension)</Link>
<PackagePath>UmbracoProject\Views\Partials\blockgrid</PackagePath>
</Content>
<Content Include="..\src\Umbraco.Web.UI\Views\_ViewImports.cshtml">
<Link>UmbracoProject\Views\_ViewImports.cshtml</Link>
<PackagePath>UmbracoProject\Views</PackagePath>