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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
19
src/Umbraco.Web.UI/Views/Partials/blockgrid/areas.cshtml
Normal file
19
src/Umbraco.Web.UI/Views/Partials/blockgrid/areas.cshtml
Normal 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>
|
||||
11
src/Umbraco.Web.UI/Views/Partials/blockgrid/default.cshtml
Normal file
11
src/Umbraco.Web.UI/Views/Partials/blockgrid/default.cshtml
Normal 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>
|
||||
39
src/Umbraco.Web.UI/Views/Partials/blockgrid/items.cshtml
Normal file
39
src/Umbraco.Web.UI/Views/Partials/blockgrid/items.cshtml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user