Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/release/15.0' into v15/dev
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockEditorPropertiesBase.cs
#	src/Umbraco.Web.UI.Client
  • Loading branch information
bergmania committed Nov 5, 2024
2 parents c8cbbcc + 983a0ec commit 336bd99
Show file tree
Hide file tree
Showing 15 changed files with 668 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions src/Umbraco.Cms.Api.Delivery/Caching/NoOutputCachePolicy.cs
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public void Configure(SwaggerGenOptions swaggerGenOptions)
});

swaggerGenOptions.DocumentFilter<MimeTypeDocumentFilter>(DeliveryApiConfiguration.ApiName);
swaggerGenOptions.DocumentFilter<RemoveSecuritySchemesDocumentFilter>(DeliveryApiConfiguration.ApiName);

swaggerGenOptions.OperationFilter<SwaggerContentDocumentationFilter>();
swaggerGenOptions.OperationFilter<SwaggerMediaDocumentationFilter>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -70,5 +53,31 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context)
}
};
}

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)
}
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private static IUmbracoBuilder AddOutputCache(this IUmbracoBuilder builder)

builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(_ => { });
options.AddBasePolicy(build => build.AddPolicy<NoOutputCachePolicy>());

if (outputCacheSettings.ContentDuration.TotalSeconds > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
6 changes: 6 additions & 0 deletions src/Umbraco.Core/Models/Blocks/IBlockReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
79 changes: 39 additions & 40 deletions src/Umbraco.Core/Services/ContentEditingService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
Expand All @@ -64,14 +35,17 @@ public 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)
Expand Down Expand Up @@ -154,28 +128,53 @@ private async Task<IContent> EnsureOnlyAllowedFieldsAreUpdated(IContent contentW

var allowedCultures = (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet();

ILanguage? defaultLanguage = await _languageService.GetDefaultLanguageAsync();

foreach (var culture in contentWithPotentialUnallowedChanges.EditedCultures ?? contentWithPotentialUnallowedChanges.PublishedCultures)
{
if (allowedCultures.Contains(culture))
{
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,25 +151,16 @@ private bool Handle(IPropertyType[] propertyTypes, IDictionary<int, ILanguage> l

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;
Expand Down Expand Up @@ -265,11 +256,37 @@ private bool Handle(IPropertyType[] propertyTypes, IDictionary<int, ILanguage> l
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);

Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Web.UI.Client
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
Expand Down
Loading

0 comments on commit 336bd99

Please sign in to comment.