Merge remote-tracking branch 'origin/release/15.0' into v15/dev
# Conflicts: # src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockEditorPropertiesBase.cs # src/Umbraco.Web.UI.Client
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// This filter explicitly removes all security schemes from a named OpenAPI document.
|
||||
/// </summary>
|
||||
public class RemoveSecuritySchemesDocumentFilter : IDocumentFilter
|
||||
{
|
||||
private readonly string _documentName;
|
||||
|
||||
public RemoveSecuritySchemesDocumentFilter(string documentName)
|
||||
=> _documentName = documentName;
|
||||
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
if (context.DocumentName != _documentName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
swaggerDoc.Components.SecuritySchemes.Clear();
|
||||
}
|
||||
}
|
||||
19
src/Umbraco.Cms.Api.Delivery/Caching/NoOutputCachePolicy.cs
Normal file
19
src/Umbraco.Cms.Api.Delivery/Caching/NoOutputCachePolicy.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.OutputCaching;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Caching;
|
||||
|
||||
internal sealed class NoOutputCachePolicy : IOutputCachePolicy
|
||||
{
|
||||
ValueTask IOutputCachePolicy.CacheRequestAsync(OutputCacheContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
context.EnableOutputCaching = false;
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
ValueTask IOutputCachePolicy.ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellationToken)
|
||||
=> ValueTask.CompletedTask;
|
||||
|
||||
ValueTask IOutputCachePolicy.ServeResponseAsync(OutputCacheContext context, CancellationToken cancellationToken)
|
||||
=> ValueTask.CompletedTask;
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public class ConfigureUmbracoDeliveryApiSwaggerGenOptions: IConfigureOptions<Swa
|
||||
});
|
||||
|
||||
swaggerGenOptions.DocumentFilter<MimeTypeDocumentFilter>(DeliveryApiConfiguration.ApiName);
|
||||
swaggerGenOptions.DocumentFilter<RemoveSecuritySchemesDocumentFilter>(DeliveryApiConfiguration.ApiName);
|
||||
|
||||
swaggerGenOptions.OperationFilter<SwaggerContentDocumentationFilter>();
|
||||
swaggerGenOptions.OperationFilter<SwaggerMediaDocumentationFilter>();
|
||||
|
||||
@@ -17,33 +17,16 @@ namespace Umbraco.Cms.Api.Delivery.Configuration;
|
||||
/// </remarks>
|
||||
public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
|
||||
{
|
||||
private const string AuthSchemeName = "Umbraco Member";
|
||||
private const string AuthSchemeName = "UmbracoMember";
|
||||
|
||||
public void Configure(SwaggerGenOptions options)
|
||||
{
|
||||
options.AddSecurityDefinition(
|
||||
AuthSchemeName,
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = AuthSchemeName,
|
||||
Type = SecuritySchemeType.OAuth2,
|
||||
Description = "Umbraco Member Authentication",
|
||||
Flows = new OpenApiOAuthFlows
|
||||
{
|
||||
AuthorizationCode = new OpenApiOAuthFlow
|
||||
{
|
||||
AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative),
|
||||
TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// add security requirements for content API operations
|
||||
options.DocumentFilter<DeliveryApiSecurityFilter>();
|
||||
options.OperationFilter<DeliveryApiSecurityFilter>();
|
||||
}
|
||||
|
||||
private class DeliveryApiSecurityFilter : SwaggerFilterBase<ContentApiControllerBase>, IOperationFilter
|
||||
private class DeliveryApiSecurityFilter : SwaggerFilterBase<ContentApiControllerBase>, IOperationFilter, IDocumentFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
@@ -70,5 +53,31 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
if (context.DocumentName != DeliveryApiConfiguration.ApiName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
swaggerDoc.Components.SecuritySchemes.Add(
|
||||
AuthSchemeName,
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = AuthSchemeName,
|
||||
Type = SecuritySchemeType.OAuth2,
|
||||
Description = "Umbraco Member Authentication",
|
||||
Flows = new OpenApiOAuthFlows
|
||||
{
|
||||
AuthorizationCode = new OpenApiOAuthFlow
|
||||
{
|
||||
AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative),
|
||||
TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public static class UmbracoBuilderExtensions
|
||||
|
||||
builder.Services.AddOutputCache(options =>
|
||||
{
|
||||
options.AddBasePolicy(_ => { });
|
||||
options.AddBasePolicy(build => build.AddPolicy<NoOutputCachePolicy>());
|
||||
|
||||
if (outputCacheSettings.ContentDuration.TotalSeconds > 0)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Umbraco.Cms.Core.Configuration;
|
||||
/// <summary>
|
||||
/// Typed configuration options for content dashboard settings.
|
||||
/// </summary>
|
||||
[Obsolete("Scheduled for removal in v16, dashboard manipulation is now done trough frontend extensions.")]
|
||||
[UmbracoOptions(Constants.Configuration.ConfigContentDashboard)]
|
||||
public class ContentDashboardSettings
|
||||
{
|
||||
|
||||
@@ -18,7 +18,13 @@ public interface IBlockReference
|
||||
/// <value>
|
||||
/// The content UDI.
|
||||
/// </value>
|
||||
[Obsolete("Use ContentKey instead. Will be removed in V18.")]
|
||||
Udi ContentUdi { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content key.
|
||||
/// </summary>
|
||||
public Guid ContentKey { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
@@ -14,42 +14,13 @@ namespace Umbraco.Cms.Core.Services;
|
||||
internal sealed class ContentEditingService
|
||||
: ContentEditingServiceWithSortingBase<IContent, IContentType, IContentService, IContentTypeService>, IContentEditingService
|
||||
{
|
||||
private readonly PropertyEditorCollection _propertyEditorCollection;
|
||||
private readonly ITemplateService _templateService;
|
||||
private readonly ILogger<ContentEditingService> _logger;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ILanguageService _languageService;
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")]
|
||||
public ContentEditingService(
|
||||
IContentService contentService,
|
||||
IContentTypeService contentTypeService,
|
||||
PropertyEditorCollection propertyEditorCollection,
|
||||
IDataTypeService dataTypeService,
|
||||
ITemplateService templateService,
|
||||
ILogger<ContentEditingService> logger,
|
||||
ICoreScopeProvider scopeProvider,
|
||||
IUserIdKeyResolver userIdKeyResolver,
|
||||
ITreeEntitySortingService treeEntitySortingService,
|
||||
IContentValidationService contentValidationService)
|
||||
: this(
|
||||
contentService,
|
||||
contentTypeService,
|
||||
propertyEditorCollection,
|
||||
dataTypeService,
|
||||
templateService,
|
||||
logger,
|
||||
scopeProvider,
|
||||
userIdKeyResolver,
|
||||
treeEntitySortingService,
|
||||
contentValidationService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IUserService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILanguageService>()
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
private readonly ContentSettings _contentSettings;
|
||||
|
||||
public ContentEditingService(
|
||||
IContentService contentService,
|
||||
@@ -64,14 +35,17 @@ internal sealed class ContentEditingService
|
||||
IContentValidationService contentValidationService,
|
||||
IUserService userService,
|
||||
ILocalizationService localizationService,
|
||||
ILanguageService languageService)
|
||||
ILanguageService languageService,
|
||||
IOptions<ContentSettings> contentSettings)
|
||||
: base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, contentValidationService, treeEntitySortingService)
|
||||
{
|
||||
_propertyEditorCollection = propertyEditorCollection;
|
||||
_templateService = templateService;
|
||||
_logger = logger;
|
||||
_userService = userService;
|
||||
_localizationService = localizationService;
|
||||
_languageService = languageService;
|
||||
_contentSettings = contentSettings.Value;
|
||||
}
|
||||
|
||||
public async Task<IContent?> GetAsync(Guid key)
|
||||
@@ -154,6 +128,8 @@ internal sealed class ContentEditingService
|
||||
|
||||
var allowedCultures = (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet();
|
||||
|
||||
ILanguage? defaultLanguage = await _languageService.GetDefaultLanguageAsync();
|
||||
|
||||
foreach (var culture in contentWithPotentialUnallowedChanges.EditedCultures ?? contentWithPotentialUnallowedChanges.PublishedCultures)
|
||||
{
|
||||
if (allowedCultures.Contains(culture))
|
||||
@@ -161,21 +137,44 @@ internal sealed class ContentEditingService
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// else override the updates values with the original values.
|
||||
foreach (IProperty property in contentWithPotentialUnallowedChanges.Properties)
|
||||
{
|
||||
if (property.PropertyType.VariesByCulture() is false)
|
||||
// if the property varies by culture, simply overwrite the edited property value with the current property value
|
||||
if (property.PropertyType.VariesByCulture())
|
||||
{
|
||||
var currentValue = existingContent?.Properties.First(x => x.Alias == property.Alias).GetValue(culture, null, false);
|
||||
property.SetValue(currentValue, culture, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = existingContent?.Properties.First(x=>x.Alias == property.Alias).GetValue(culture, null, false);
|
||||
property.SetValue(value, culture, null);
|
||||
// if the property does not vary by culture and the data editor supports variance within invariant property values,
|
||||
// we need perform a merge between the edited property value and the current property value
|
||||
if (_propertyEditorCollection.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) && dataEditor.CanMergePartialPropertyValues(property.PropertyType))
|
||||
{
|
||||
var currentValue = existingContent?.Properties.First(x => x.Alias == property.Alias).GetValue(null, null, false);
|
||||
var editedValue = contentWithPotentialUnallowedChanges.Properties.First(x => x.Alias == property.Alias).GetValue(null, null, false);
|
||||
var mergedValue = dataEditor.MergePartialPropertyValueForCulture(currentValue, editedValue, culture);
|
||||
|
||||
// If we are not allowed to edit invariant properties, overwrite the edited property value with the current property value.
|
||||
if (_contentSettings.AllowEditInvariantFromNonDefault is false && culture == defaultLanguage?.IsoCode)
|
||||
{
|
||||
mergedValue = dataEditor.MergePartialPropertyValueForCulture(currentValue, mergedValue, null);
|
||||
}
|
||||
|
||||
property.SetValue(mergedValue, null, null);
|
||||
}
|
||||
|
||||
// If property does not support merging, we still need to overwrite if we are not allowed to edit invariant properties.
|
||||
else if (_contentSettings.AllowEditInvariantFromNonDefault is false && culture == defaultLanguage?.IsoCode)
|
||||
{
|
||||
var currentValue = existingContent?.Properties.First(x => x.Alias == property.Alias).GetValue(null, null, false);
|
||||
property.SetValue(currentValue, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return contentWithPotentialUnallowedChanges;
|
||||
return contentWithPotentialUnallowedChanges;
|
||||
}
|
||||
|
||||
public async Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey)
|
||||
|
||||
@@ -151,25 +151,16 @@ public abstract class ConvertBlockEditorPropertiesBase : MigrationBase
|
||||
|
||||
var progress = 0;
|
||||
|
||||
Parallel.ForEachAsync(updateBatch, async (update, token) =>
|
||||
void HandleUpdateBatch(UpdateBatch<PropertyDataDto> update)
|
||||
{
|
||||
//Foreach here, but we need to suppress the flow before each task, but not the actual await of the task
|
||||
Task task;
|
||||
using (ExecutionContext.SuppressFlow())
|
||||
{
|
||||
task = Task.Run(
|
||||
() =>
|
||||
{
|
||||
using ICoreScope scope = _coreScopeProvider.CreateCoreScope();
|
||||
scope.Complete();
|
||||
|
||||
using UmbracoContextReference umbracoContextReference =
|
||||
_umbracoContextFactory.EnsureUmbracoContext();
|
||||
|
||||
progress++;
|
||||
if (progress % 100 == 0)
|
||||
{
|
||||
_logger.LogInformation(" - finíshed {progress} of {total} properties", progress,
|
||||
updateBatch.Count);
|
||||
_logger.LogInformation(" - finíshed {progress} of {total} properties", progress, updateBatch.Count);
|
||||
}
|
||||
|
||||
PropertyDataDto propertyDataDto = update.Poco;
|
||||
@@ -265,11 +256,37 @@ public abstract class ConvertBlockEditorPropertiesBase : MigrationBase
|
||||
stringValue = UpdateDatabaseValue(stringValue);
|
||||
|
||||
propertyDataDto.TextValue = stringValue;
|
||||
}, token);
|
||||
}
|
||||
}
|
||||
|
||||
await task;
|
||||
}).GetAwaiter().GetResult();
|
||||
if (DatabaseType == DatabaseType.SQLite)
|
||||
{
|
||||
// SQLite locks up if we run the migration in parallel, so... let's not.
|
||||
foreach (UpdateBatch<PropertyDataDto> update in updateBatch)
|
||||
{
|
||||
HandleUpdateBatch(update);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Parallel.ForEachAsync(updateBatch, async (update, token) =>
|
||||
{
|
||||
//Foreach here, but we need to suppress the flow before each task, but not the actuall await of the task
|
||||
Task task;
|
||||
using (ExecutionContext.SuppressFlow())
|
||||
{
|
||||
task = Task.Run(
|
||||
() =>
|
||||
{
|
||||
using ICoreScope scope = _coreScopeProvider.CreateCoreScope();
|
||||
scope.Complete();
|
||||
HandleUpdateBatch(update);
|
||||
},
|
||||
token);
|
||||
}
|
||||
|
||||
await task;
|
||||
}).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
updateBatch.RemoveAll(updatesToSkip.Contains);
|
||||
|
||||
|
||||
Submodule src/Umbraco.Web.UI.Client updated: 2d60a9361a...29583d3d34
@@ -37,6 +37,8 @@ public abstract class BlockEditorElementVariationTestBase : UmbracoIntegrationTe
|
||||
|
||||
protected PropertyEditorCollection PropertyEditorCollection => GetRequiredService<PropertyEditorCollection>();
|
||||
|
||||
protected IContentEditingService ContentEditingService => GetRequiredService<IContentEditingService>();
|
||||
|
||||
private IUmbracoContextAccessor UmbracoContextAccessor => GetRequiredService<IUmbracoContextAccessor>();
|
||||
|
||||
private IUmbracoContextFactory UmbracoContextFactory => GetRequiredService<IUmbracoContextFactory>();
|
||||
|
||||
@@ -0,0 +1,501 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Blocks;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
|
||||
|
||||
public partial class BlockListElementLevelVariationTests
|
||||
{
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public async Task Can_Handle_Limited_User_Access_To_Languages_With_AllowEditInvariantFromNonDefault(bool updateWithLimitedUserAccess)
|
||||
{
|
||||
await LanguageService.CreateAsync(
|
||||
new Language("de-DE", "German"), Constants.Security.SuperUserKey);
|
||||
var userKey = updateWithLimitedUserAccess
|
||||
? (await CreateLimitedUser()).Key
|
||||
: Constants.Security.SuperUserKey;
|
||||
|
||||
var elementType = CreateElementType(ContentVariation.Culture);
|
||||
var blockListDataType = await CreateBlockListDataType(elementType);
|
||||
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
|
||||
var content = CreateContent(contentType, elementType, [], false);
|
||||
content.SetCultureName("Home (de)", "de-DE");
|
||||
ContentService.Save(content);
|
||||
|
||||
var blockListValue = BlockListPropertyValue(
|
||||
elementType,
|
||||
[
|
||||
(
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid(),
|
||||
new BlockProperty(
|
||||
new List<BlockPropertyValue> {
|
||||
new() { Alias = "invariantText", Value = "#1: The first invariant content value" },
|
||||
new() { Alias = "variantText", Value = "#1: The first content value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "#1: The first content value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "#1: The first content value in German", Culture = "de-DE" }
|
||||
},
|
||||
new List<BlockPropertyValue> {
|
||||
new() { Alias = "invariantText", Value = "#1: The first invariant settings value" },
|
||||
new() { Alias = "variantText", Value = "#1: The first settings value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "#1: The first settings value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "#1: The first settings value in German", Culture = "de-DE" }
|
||||
},
|
||||
null,
|
||||
null
|
||||
)
|
||||
),
|
||||
(
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid(),
|
||||
new BlockProperty(
|
||||
new List<BlockPropertyValue> {
|
||||
new() { Alias = "invariantText", Value = "#2: The first invariant content value" },
|
||||
new() { Alias = "variantText", Value = "#2: The first content value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "#2: The first content value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "#2: The first content value in German", Culture = "de-DE" }
|
||||
},
|
||||
new List<BlockPropertyValue> {
|
||||
new() { Alias = "invariantText", Value = "#2: The first invariant settings value" },
|
||||
new() { Alias = "variantText", Value = "#2: The first settings value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "#2: The first settings value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "#2: The first settings value in German", Culture = "de-DE" }
|
||||
},
|
||||
null,
|
||||
null
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
|
||||
ContentService.Save(content);
|
||||
|
||||
blockListValue.ContentData[0].Values[0].Value = "#1: The second invariant content value";
|
||||
blockListValue.ContentData[0].Values[1].Value = "#1: The second content value in English";
|
||||
blockListValue.ContentData[0].Values[2].Value = "#1: The second content value in Danish";
|
||||
blockListValue.ContentData[0].Values[3].Value = "#1: The second content value in German";
|
||||
blockListValue.SettingsData[0].Values[0].Value = "#1: The second invariant settings value";
|
||||
blockListValue.SettingsData[0].Values[1].Value = "#1: The second settings value in English";
|
||||
blockListValue.SettingsData[0].Values[2].Value = "#1: The second settings value in Danish";
|
||||
blockListValue.SettingsData[0].Values[3].Value = "#1: The second settings value in German";
|
||||
|
||||
blockListValue.ContentData[1].Values[0].Value = "#2: The second invariant content value";
|
||||
blockListValue.ContentData[1].Values[1].Value = "#2: The second content value in English";
|
||||
blockListValue.ContentData[1].Values[2].Value = "#2: The second content value in Danish";
|
||||
blockListValue.ContentData[1].Values[3].Value = "#2: The second content value in German";
|
||||
blockListValue.SettingsData[1].Values[0].Value = "#2: The second invariant settings value";
|
||||
blockListValue.SettingsData[1].Values[1].Value = "#2: The second settings value in English";
|
||||
blockListValue.SettingsData[1].Values[2].Value = "#2: The second settings value in Danish";
|
||||
blockListValue.SettingsData[1].Values[3].Value = "#2: The second settings value in German";
|
||||
|
||||
var updateModel = new ContentUpdateModel
|
||||
{
|
||||
InvariantProperties = new[]
|
||||
{
|
||||
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
|
||||
},
|
||||
Variants = new[]
|
||||
{
|
||||
new VariantModel { Name = content.GetCultureName("en-US")!, Culture = "en-US", Properties = [] },
|
||||
new VariantModel { Name = content.GetCultureName("da-DK")!, Culture = "da-DK", Properties = [] },
|
||||
new VariantModel { Name = content.GetCultureName("de-DE")!, Culture = "de-DE", Properties = [] }
|
||||
}
|
||||
};
|
||||
|
||||
var result = await ContentEditingService.UpdateAsync(content.Key, updateModel, userKey);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
content = ContentService.GetById(content.Key);
|
||||
var savedBlocksValue = content?.Properties["blocks"]?.GetValue()?.ToString();
|
||||
Assert.NotNull(savedBlocksValue);
|
||||
blockListValue = JsonSerializer.Deserialize<BlockListValue>(savedBlocksValue);
|
||||
|
||||
// the Danish and invariant values should be updated regardless of the executing user
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("#1: The second invariant content value", blockListValue.ContentData[0].Values[0].Value);
|
||||
Assert.AreEqual("#1: The second content value in Danish", blockListValue.ContentData[0].Values[2].Value);
|
||||
Assert.AreEqual("#1: The second invariant settings value", blockListValue.SettingsData[0].Values[0].Value);
|
||||
Assert.AreEqual("#1: The second settings value in Danish", blockListValue.SettingsData[0].Values[2].Value);
|
||||
|
||||
Assert.AreEqual("#2: The second invariant content value", blockListValue.ContentData[1].Values[0].Value);
|
||||
Assert.AreEqual("#2: The second content value in Danish", blockListValue.ContentData[1].Values[2].Value);
|
||||
Assert.AreEqual("#2: The second invariant settings value", blockListValue.SettingsData[1].Values[0].Value);
|
||||
Assert.AreEqual("#2: The second settings value in Danish", blockListValue.SettingsData[1].Values[2].Value);
|
||||
});
|
||||
|
||||
// limited user access means English and German should not have been updated - changes should be rolled back to the initial block values
|
||||
if (updateWithLimitedUserAccess)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("#1: The first content value in English", blockListValue.ContentData[0].Values[1].Value);
|
||||
Assert.AreEqual("#1: The first settings value in English", blockListValue.SettingsData[0].Values[1].Value);
|
||||
Assert.AreEqual("#1: The first content value in German", blockListValue.ContentData[0].Values[3].Value);
|
||||
Assert.AreEqual("#1: The first settings value in German", blockListValue.SettingsData[0].Values[3].Value);
|
||||
|
||||
Assert.AreEqual("#2: The first content value in English", blockListValue.ContentData[1].Values[1].Value);
|
||||
Assert.AreEqual("#2: The first settings value in English", blockListValue.SettingsData[1].Values[1].Value);
|
||||
Assert.AreEqual("#2: The first content value in German", blockListValue.ContentData[1].Values[3].Value);
|
||||
Assert.AreEqual("#2: The first settings value in German", blockListValue.SettingsData[1].Values[3].Value);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("#1: The second content value in English", blockListValue.ContentData[0].Values[1].Value);
|
||||
Assert.AreEqual("#1: The second settings value in English", blockListValue.SettingsData[0].Values[1].Value);
|
||||
Assert.AreEqual("#1: The second content value in German", blockListValue.ContentData[0].Values[3].Value);
|
||||
Assert.AreEqual("#1: The second settings value in German", blockListValue.SettingsData[0].Values[3].Value);
|
||||
|
||||
Assert.AreEqual("#2: The second content value in English", blockListValue.ContentData[1].Values[1].Value);
|
||||
Assert.AreEqual("#2: The second settings value in English", blockListValue.SettingsData[1].Values[1].Value);
|
||||
Assert.AreEqual("#2: The second content value in German", blockListValue.ContentData[1].Values[3].Value);
|
||||
Assert.AreEqual("#2: The second settings value in German", blockListValue.SettingsData[1].Values[3].Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public async Task Can_Handle_Limited_User_Access_To_Languages_Without_AllowEditInvariantFromNonDefault(bool updateWithLimitedUserAccess)
|
||||
{
|
||||
await LanguageService.CreateAsync(
|
||||
new Language("de-DE", "German"), Constants.Security.SuperUserKey);
|
||||
var userKey = updateWithLimitedUserAccess
|
||||
? (await CreateLimitedUser()).Key
|
||||
: Constants.Security.SuperUserKey;
|
||||
|
||||
var elementType = CreateElementType(ContentVariation.Culture);
|
||||
var blockListDataType = await CreateBlockListDataType(elementType);
|
||||
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
|
||||
var content = CreateContent(contentType, elementType, [], false);
|
||||
content.SetCultureName("Home (de)", "de-DE");
|
||||
ContentService.Save(content);
|
||||
|
||||
var blockListValue = BlockListPropertyValue(
|
||||
elementType,
|
||||
[
|
||||
(
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid(),
|
||||
new BlockProperty(
|
||||
new List<BlockPropertyValue> {
|
||||
new() { Alias = "invariantText", Value = "#1: The first invariant content value" },
|
||||
new() { Alias = "variantText", Value = "#1: The first content value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "#1: The first content value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "#1: The first content value in German", Culture = "de-DE" }
|
||||
},
|
||||
new List<BlockPropertyValue> {
|
||||
new() { Alias = "invariantText", Value = "#1: The first invariant settings value" },
|
||||
new() { Alias = "variantText", Value = "#1: The first settings value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "#1: The first settings value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "#1: The first settings value in German", Culture = "de-DE" }
|
||||
},
|
||||
null,
|
||||
null
|
||||
)
|
||||
),
|
||||
(
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid(),
|
||||
new BlockProperty(
|
||||
new List<BlockPropertyValue> {
|
||||
new() { Alias = "invariantText", Value = "#2: The first invariant content value" },
|
||||
new() { Alias = "variantText", Value = "#2: The first content value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "#2: The first content value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "#2: The first content value in German", Culture = "de-DE" }
|
||||
},
|
||||
new List<BlockPropertyValue> {
|
||||
new() { Alias = "invariantText", Value = "#2: The first invariant settings value" },
|
||||
new() { Alias = "variantText", Value = "#2: The first settings value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "#2: The first settings value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "#2: The first settings value in German", Culture = "de-DE" }
|
||||
},
|
||||
null,
|
||||
null
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
|
||||
ContentService.Save(content);
|
||||
|
||||
blockListValue.ContentData[0].Values[0].Value = "#1: The second invariant content value";
|
||||
blockListValue.ContentData[0].Values[1].Value = "#1: The second content value in English";
|
||||
blockListValue.ContentData[0].Values[2].Value = "#1: The second content value in Danish";
|
||||
blockListValue.ContentData[0].Values[3].Value = "#1: The second content value in German";
|
||||
blockListValue.SettingsData[0].Values[0].Value = "#1: The second invariant settings value";
|
||||
blockListValue.SettingsData[0].Values[1].Value = "#1: The second settings value in English";
|
||||
blockListValue.SettingsData[0].Values[2].Value = "#1: The second settings value in Danish";
|
||||
blockListValue.SettingsData[0].Values[3].Value = "#1: The second settings value in German";
|
||||
|
||||
blockListValue.ContentData[1].Values[0].Value = "#2: The second invariant content value";
|
||||
blockListValue.ContentData[1].Values[1].Value = "#2: The second content value in English";
|
||||
blockListValue.ContentData[1].Values[2].Value = "#2: The second content value in Danish";
|
||||
blockListValue.ContentData[1].Values[3].Value = "#2: The second content value in German";
|
||||
blockListValue.SettingsData[1].Values[0].Value = "#2: The second invariant settings value";
|
||||
blockListValue.SettingsData[1].Values[1].Value = "#2: The second settings value in English";
|
||||
blockListValue.SettingsData[1].Values[2].Value = "#2: The second settings value in Danish";
|
||||
blockListValue.SettingsData[1].Values[3].Value = "#2: The second settings value in German";
|
||||
|
||||
var updateModel = new ContentUpdateModel
|
||||
{
|
||||
InvariantProperties = new[]
|
||||
{
|
||||
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
|
||||
},
|
||||
Variants = new[]
|
||||
{
|
||||
new VariantModel { Name = content.GetCultureName("en-US")!, Culture = "en-US", Properties = [] },
|
||||
new VariantModel { Name = content.GetCultureName("da-DK")!, Culture = "da-DK", Properties = [] },
|
||||
new VariantModel { Name = content.GetCultureName("de-DE")!, Culture = "de-DE", Properties = [] }
|
||||
}
|
||||
};
|
||||
|
||||
var result = await ContentEditingService.UpdateAsync(content.Key, updateModel, userKey);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
content = ContentService.GetById(content.Key);
|
||||
var savedBlocksValue = content?.Properties["blocks"]?.GetValue()?.ToString();
|
||||
Assert.NotNull(savedBlocksValue);
|
||||
blockListValue = JsonSerializer.Deserialize<BlockListValue>(savedBlocksValue);
|
||||
|
||||
// the Danish values should be updated regardless of the executing user
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("#1: The second content value in Danish", blockListValue.ContentData[0].Values[2].Value);
|
||||
Assert.AreEqual("#1: The second settings value in Danish", blockListValue.SettingsData[0].Values[2].Value);
|
||||
|
||||
Assert.AreEqual("#2: The second content value in Danish", blockListValue.ContentData[1].Values[2].Value);
|
||||
Assert.AreEqual("#2: The second settings value in Danish", blockListValue.SettingsData[1].Values[2].Value);
|
||||
});
|
||||
|
||||
// limited user access means invariant, English and German should not have been updated - changes should be rolled back to the initial block values
|
||||
if (updateWithLimitedUserAccess)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
||||
Assert.AreEqual("#1: The first invariant content value", blockListValue.ContentData[0].Values[0].Value);
|
||||
Assert.AreEqual("#1: The first invariant settings value", blockListValue.SettingsData[0].Values[0].Value);
|
||||
Assert.AreEqual("#1: The first content value in English", blockListValue.ContentData[0].Values[1].Value);
|
||||
Assert.AreEqual("#1: The first settings value in English", blockListValue.SettingsData[0].Values[1].Value);
|
||||
Assert.AreEqual("#1: The first content value in German", blockListValue.ContentData[0].Values[3].Value);
|
||||
Assert.AreEqual("#1: The first settings value in German", blockListValue.SettingsData[0].Values[3].Value);
|
||||
|
||||
Assert.AreEqual("#2: The first invariant content value", blockListValue.ContentData[1].Values[0].Value);
|
||||
Assert.AreEqual("#2: The first invariant settings value", blockListValue.SettingsData[1].Values[0].Value);
|
||||
Assert.AreEqual("#2: The first content value in English", blockListValue.ContentData[1].Values[1].Value);
|
||||
Assert.AreEqual("#2: The first settings value in English", blockListValue.SettingsData[1].Values[1].Value);
|
||||
Assert.AreEqual("#2: The first content value in German", blockListValue.ContentData[1].Values[3].Value);
|
||||
Assert.AreEqual("#2: The first settings value in German", blockListValue.SettingsData[1].Values[3].Value);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("#1: The second invariant content value", blockListValue.ContentData[0].Values[0].Value);
|
||||
Assert.AreEqual("#1: The second invariant settings value", blockListValue.SettingsData[0].Values[0].Value);
|
||||
Assert.AreEqual("#1: The second content value in English", blockListValue.ContentData[0].Values[1].Value);
|
||||
Assert.AreEqual("#1: The second settings value in English", blockListValue.SettingsData[0].Values[1].Value);
|
||||
Assert.AreEqual("#1: The second content value in German", blockListValue.ContentData[0].Values[3].Value);
|
||||
Assert.AreEqual("#1: The second settings value in German", blockListValue.SettingsData[0].Values[3].Value);
|
||||
|
||||
Assert.AreEqual("#2: The second invariant content value", blockListValue.ContentData[1].Values[0].Value);
|
||||
Assert.AreEqual("#2: The second invariant settings value", blockListValue.SettingsData[1].Values[0].Value);
|
||||
Assert.AreEqual("#2: The second content value in English", blockListValue.ContentData[1].Values[1].Value);
|
||||
Assert.AreEqual("#2: The second settings value in English", blockListValue.SettingsData[1].Values[1].Value);
|
||||
Assert.AreEqual("#2: The second content value in German", blockListValue.ContentData[1].Values[3].Value);
|
||||
Assert.AreEqual("#2: The second settings value in German", blockListValue.SettingsData[1].Values[3].Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public async Task Can_Handle_Limited_User_Access_To_Languages_In_Nested_Blocks_Without_Access_With_AllowEditInvariantFromNonDefault(bool updateWithLimitedUserAccess)
|
||||
{
|
||||
await LanguageService.CreateAsync(
|
||||
new Language("de-DE", "German"), Constants.Security.SuperUserKey);
|
||||
var userKey = updateWithLimitedUserAccess
|
||||
? (await CreateLimitedUser()).Key
|
||||
: Constants.Security.SuperUserKey;
|
||||
var nestedElementType = CreateElementType(ContentVariation.Culture);
|
||||
var nestedBlockListDataType = await CreateBlockListDataType(nestedElementType);
|
||||
|
||||
var rootElementType = new ContentTypeBuilder()
|
||||
.WithAlias("myRootElementType")
|
||||
.WithName("My Root Element Type")
|
||||
.WithIsElement(true)
|
||||
.WithContentVariation(ContentVariation.Culture)
|
||||
.AddPropertyType()
|
||||
.WithAlias("nestedBlocks")
|
||||
.WithName("Nested blocks")
|
||||
.WithDataTypeId(nestedBlockListDataType.Id)
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.BlockList)
|
||||
.WithValueStorageType(ValueStorageType.Ntext)
|
||||
.WithVariations(ContentVariation.Nothing)
|
||||
.Done()
|
||||
.Build();
|
||||
ContentTypeService.Save(rootElementType);
|
||||
var rootBlockListDataType = await CreateBlockListDataType(rootElementType);
|
||||
var contentType = CreateContentType(ContentVariation.Culture, rootBlockListDataType);
|
||||
|
||||
var nestedElementContentKey = Guid.NewGuid();
|
||||
var nestedElementSettingsKey = Guid.NewGuid();
|
||||
var content = CreateContent(
|
||||
contentType,
|
||||
rootElementType,
|
||||
new List<BlockPropertyValue>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Alias = "nestedBlocks",
|
||||
Value = BlockListPropertyValue(
|
||||
nestedElementType,
|
||||
nestedElementContentKey,
|
||||
nestedElementSettingsKey,
|
||||
new BlockProperty(
|
||||
new List<BlockPropertyValue>
|
||||
{
|
||||
new() { Alias = "invariantText", Value = "The first nested invariant content value" },
|
||||
new() { Alias = "variantText", Value = "The first nested content value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "The first nested content value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "The first nested content value in German", Culture = "de-DE" },
|
||||
},
|
||||
new List<BlockPropertyValue>
|
||||
{
|
||||
new() { Alias = "invariantText", Value = "The first nested invariant settings value" },
|
||||
new() { Alias = "variantText", Value = "The first nested settings value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "The first nested settings value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "The first nested settings value in German", Culture = "de-DE" },
|
||||
},
|
||||
null,
|
||||
null))
|
||||
}
|
||||
},
|
||||
[],
|
||||
false);
|
||||
content.SetCultureName("Home (de)", "de-DE");
|
||||
ContentService.Save(content);
|
||||
|
||||
var blockListValue = JsonSerializer.Deserialize<BlockListValue>((string)content.Properties["blocks"]!.GetValue()!);
|
||||
blockListValue.ContentData[0].Values[0].Value = BlockListPropertyValue(
|
||||
nestedElementType,
|
||||
nestedElementContentKey,
|
||||
nestedElementSettingsKey,
|
||||
new BlockProperty(
|
||||
new List<BlockPropertyValue>
|
||||
{
|
||||
new() { Alias = "invariantText", Value = "The second nested invariant content value" },
|
||||
new() { Alias = "variantText", Value = "The second nested content value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "The second nested content value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "The second nested content value in German", Culture = "de-DE" },
|
||||
},
|
||||
new List<BlockPropertyValue>
|
||||
{
|
||||
new() { Alias = "invariantText", Value = "The second nested invariant settings value" },
|
||||
new() { Alias = "variantText", Value = "The second nested settings value in English", Culture = "en-US" },
|
||||
new() { Alias = "variantText", Value = "The second nested settings value in Danish", Culture = "da-DK" },
|
||||
new() { Alias = "variantText", Value = "The second nested settings value in German", Culture = "de-DE" },
|
||||
},
|
||||
null,
|
||||
null));
|
||||
|
||||
var updateModel = new ContentUpdateModel
|
||||
{
|
||||
InvariantProperties = new[]
|
||||
{
|
||||
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
|
||||
},
|
||||
Variants = new[]
|
||||
{
|
||||
new VariantModel { Name = content.GetCultureName("en-US")!, Culture = "en-US", Properties = [] },
|
||||
new VariantModel { Name = content.GetCultureName("da-DK")!, Culture = "da-DK", Properties = [] },
|
||||
new VariantModel { Name = content.GetCultureName("de-DE")!, Culture = "de-DE", Properties = [] }
|
||||
}
|
||||
};
|
||||
|
||||
var result = await ContentEditingService.UpdateAsync(content.Key, updateModel, userKey);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
content = ContentService.GetById(content.Key);
|
||||
var savedBlocksValue = content?.Properties["blocks"]?.GetValue()?.ToString();
|
||||
Assert.NotNull(savedBlocksValue);
|
||||
blockListValue = JsonSerializer.Deserialize<BlockListValue>(savedBlocksValue);
|
||||
|
||||
var nestedBlocksPropertyValue = blockListValue.ContentData
|
||||
.FirstOrDefault()?.Values
|
||||
.FirstOrDefault(v => v.Alias == "nestedBlocks")?.Value?.ToString();
|
||||
Assert.IsNotNull(nestedBlocksPropertyValue);
|
||||
var nestedBlockListValue = JsonSerializer.Deserialize<BlockListValue>(nestedBlocksPropertyValue);
|
||||
|
||||
|
||||
// the Danish and invariant values should be updated regardless of the executing user
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("The second nested invariant content value", nestedBlockListValue.ContentData[0].Values[0].Value);
|
||||
Assert.AreEqual("The second nested content value in Danish", nestedBlockListValue.ContentData[0].Values[2].Value);
|
||||
|
||||
Assert.AreEqual("The second nested invariant settings value", nestedBlockListValue.SettingsData[0].Values[0].Value);
|
||||
Assert.AreEqual("The second nested settings value in Danish", nestedBlockListValue.SettingsData[0].Values[2].Value);
|
||||
});
|
||||
|
||||
// limited user access means English and German should not have been updated - changes should be rolled back to the initial block values
|
||||
if (updateWithLimitedUserAccess)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("The first nested content value in English", nestedBlockListValue.ContentData[0].Values[1].Value);
|
||||
Assert.AreEqual("The first nested content value in German", nestedBlockListValue.ContentData[0].Values[3].Value);
|
||||
|
||||
Assert.AreEqual("The first nested settings value in English", nestedBlockListValue.SettingsData[0].Values[1].Value);
|
||||
Assert.AreEqual("The first nested settings value in German", nestedBlockListValue.SettingsData[0].Values[3].Value);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("The second nested content value in English", nestedBlockListValue.ContentData[0].Values[1].Value);
|
||||
Assert.AreEqual("The second nested content value in German", nestedBlockListValue.ContentData[0].Values[3].Value);
|
||||
|
||||
Assert.AreEqual("The second nested settings value in English", nestedBlockListValue.SettingsData[0].Values[1].Value);
|
||||
Assert.AreEqual("The second nested settings value in German", nestedBlockListValue.SettingsData[0].Values[3].Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IUser> CreateLimitedUser()
|
||||
{
|
||||
var userGroupService = GetRequiredService<IUserGroupService>();
|
||||
var userService = GetRequiredService<IUserService>();
|
||||
|
||||
var danish = await LanguageService.GetAsync("da-DK");
|
||||
Assert.IsNotNull(danish);
|
||||
|
||||
var user = UserBuilder.CreateUser();
|
||||
userService.Save(user);
|
||||
|
||||
var group = UserGroupBuilder.CreateUserGroup();
|
||||
group.ClearAllowedLanguages();
|
||||
group.AddAllowedLanguage(danish.Id);
|
||||
|
||||
var userGroupResult = await userGroupService.CreateAsync(group, Constants.Security.SuperUserKey, [user.Key]);
|
||||
Assert.IsTrue(userGroupResult.Success);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,12 @@ public partial class BlockListElementLevelVariationTests : BlockEditorElementVar
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Publish_Invariant_Properties_Without_Default_Culture_With_AllowEditInvariantFromNonDefault));
|
||||
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_With_AllowEditInvariantFromNonDefault));
|
||||
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_In_Nested_Blocks_Without_Access_With_AllowEditInvariantFromNonDefault));
|
||||
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_With_AllowEditInvariantFromNonDefault) + "(True)");
|
||||
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_With_AllowEditInvariantFromNonDefault) + "(False)");
|
||||
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_In_Nested_Blocks_Without_Access_With_AllowEditInvariantFromNonDefault) + "(True)");
|
||||
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_In_Nested_Blocks_Without_Access_With_AllowEditInvariantFromNonDefault) + "(False)");
|
||||
}
|
||||
|
||||
private IJsonSerializer JsonSerializer => GetRequiredService<IJsonSerializer>();
|
||||
|
||||
@@ -167,6 +167,9 @@
|
||||
<Compile Update="Umbraco.Infrastructure\PropertyEditors\BlockListElementLevelVariationTests.Indexing.cs">
|
||||
<DependentUpon>BlockListElementLevelVariationTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Infrastructure\PropertyEditors\BlockListElementLevelVariationTests.Editing.cs">
|
||||
<DependentUpon>BlockListElementLevelVariationTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Infrastructure\PropertyEditors\BlockListElementLevelVariationTests.Parsing.cs">
|
||||
<DependentUpon>BlockListElementLevelVariationTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
||||
@@ -67,6 +67,7 @@ internal class UmbracoCmsSchema
|
||||
|
||||
public required LegacyPasswordMigrationSettings LegacyPasswordMigration { get; set; }
|
||||
|
||||
[Obsolete("Scheduled for removal in v16, dashboard manipulation is now done trough frontend extensions.")]
|
||||
public required ContentDashboardSettings ContentDashboard { get; set; }
|
||||
|
||||
public required HelpPageSettings HelpPage { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user