Add endpoint for retrieving allowed media types for file extensions (#16189)
* Add endpoint for retrieving allowed media types for file extensions * Moved paging into service --------- Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MediaType.Item;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Item;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AllowedMediaTypeItemController : MediaTypeItemControllerBase
|
||||
{
|
||||
private readonly IMediaTypeEditingService _mediaTypeEditingService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
|
||||
public AllowedMediaTypeItemController(IMediaTypeEditingService mediaTypeEditingService, IUmbracoMapper mapper)
|
||||
{
|
||||
_mediaTypeEditingService = mediaTypeEditingService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("allowed")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<MediaTypeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Item(CancellationToken cancellationToken, string fileExtension, int skip = 0, int take = 100)
|
||||
{
|
||||
PagedModel<IMediaType> mediaTypes = await _mediaTypeEditingService.GetMediaTypesForFileExtensionAsync(fileExtension, skip, take);
|
||||
|
||||
var result = new PagedModel<MediaTypeItemResponseModel>
|
||||
{
|
||||
Items = _mapper.MapEnumerable<IMediaType, MediaTypeItemResponseModel>(mediaTypes.Items),
|
||||
Total = mediaTypes.Total
|
||||
};
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
@@ -12794,6 +12794,68 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/item/media-type/allowed": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Media Type"
|
||||
],
|
||||
"operationId": "GetItemMediaTypeAllowed",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "fileExtension",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"default": 100
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/PagedModelMediaTypeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
},
|
||||
"403": {
|
||||
"description": "The authenticated user do not have access to this resource"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/item/media-type/search": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
||||
@@ -14,4 +14,6 @@ public interface IMediaTypeEditingService
|
||||
Guid? key,
|
||||
IEnumerable<Guid> currentCompositeKeys,
|
||||
IEnumerable<string> currentPropertyAliases);
|
||||
|
||||
Task<PagedModel<IMediaType>> GetMediaTypesForFileExtensionAsync(string fileExtension, int skip, int take);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Media;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
|
||||
internal sealed class MediaTypeEditingService : ContentTypeEditingServiceBase<IMediaType, IMediaTypeService, MediaTypePropertyTypeModel, MediaTypePropertyContainerModel>, IMediaTypeEditingService
|
||||
{
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IImageUrlGenerator _imageUrlGenerator;
|
||||
|
||||
public MediaTypeEditingService(
|
||||
IContentTypeService contentTypeService,
|
||||
IMediaTypeService mediaTypeService,
|
||||
IDataTypeService dataTypeService,
|
||||
IEntityService entityService,
|
||||
IShortStringHelper shortStringHelper)
|
||||
IShortStringHelper shortStringHelper,
|
||||
IImageUrlGenerator imageUrlGenerator)
|
||||
: base(contentTypeService, mediaTypeService, dataTypeService, entityService, shortStringHelper)
|
||||
=> _mediaTypeService = mediaTypeService;
|
||||
{
|
||||
_mediaTypeService = mediaTypeService;
|
||||
_dataTypeService = dataTypeService;
|
||||
_imageUrlGenerator = imageUrlGenerator;
|
||||
}
|
||||
|
||||
public async Task<Attempt<IMediaType?, ContentTypeOperationStatus>> CreateAsync(MediaTypeCreateModel model, Guid userKey)
|
||||
{
|
||||
@@ -48,6 +58,50 @@ internal sealed class MediaTypeEditingService : ContentTypeEditingServiceBase<IM
|
||||
IEnumerable<string> currentPropertyAliases) =>
|
||||
await FindAvailableCompositionsAsync(key, currentCompositeKeys, currentPropertyAliases);
|
||||
|
||||
public async Task<PagedModel<IMediaType>> GetMediaTypesForFileExtensionAsync(string fileExtension, int skip, int take)
|
||||
{
|
||||
fileExtension = fileExtension.TrimStart(Constants.CharArrays.Period);
|
||||
|
||||
IMediaType[] candidateMediaTypes = _mediaTypeService.GetAll().Where(mt => mt.CompositionPropertyTypes.Any(pt => pt.Alias == Constants.Conventions.Media.File)).ToArray();
|
||||
var allowedMediaTypes = new List<IMediaType>();
|
||||
|
||||
// is this an image format supported by the image cropper?
|
||||
if (_imageUrlGenerator.IsSupportedImageFormat(fileExtension))
|
||||
{
|
||||
// yes - add all media types with an image cropper "file" property
|
||||
allowedMediaTypes.AddRange(candidateMediaTypes
|
||||
.Where(mt => mt.CompositionPropertyTypes.Any(propertyType => propertyType is
|
||||
{
|
||||
Alias: Constants.Conventions.Media.File,
|
||||
PropertyEditorAlias: Constants.PropertyEditors.Aliases.ImageCropper
|
||||
})));
|
||||
}
|
||||
|
||||
// find media types that have an explicit allow-list of file extensions
|
||||
// - NOTE: an empty allow-list should be interpreted as "all file extensions are allowed"
|
||||
IDictionary<IMediaType, IEnumerable<string>> allowedFileExtensionsByMediaType = await FetchAllowedFileExtensionsByMediaTypeAsync(candidateMediaTypes);
|
||||
|
||||
// add all media types where the file extension is explicitly allowed
|
||||
allowedMediaTypes.AddRange(allowedFileExtensionsByMediaType
|
||||
.Where(kvp => kvp.Value.Contains(fileExtension))
|
||||
.Select(kvp => kvp.Key));
|
||||
|
||||
// if we at this point have no allowed media types, add all media types that allow any file extension
|
||||
if (allowedMediaTypes.Any() is false)
|
||||
{
|
||||
allowedMediaTypes.AddRange(allowedFileExtensionsByMediaType
|
||||
.Where(kvp => kvp.Value.Any() is false)
|
||||
.Select(kvp => kvp.Key));
|
||||
}
|
||||
|
||||
return new PagedModel<IMediaType>()
|
||||
{
|
||||
Items = allowedMediaTypes.Skip(skip).Take(take),
|
||||
Total = allowedMediaTypes.Count
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
protected override IMediaType CreateContentType(IShortStringHelper shortStringHelper, int parentId)
|
||||
=> new MediaType(shortStringHelper, parentId);
|
||||
|
||||
@@ -56,4 +110,35 @@ internal sealed class MediaTypeEditingService : ContentTypeEditingServiceBase<IM
|
||||
protected override UmbracoObjectTypes ContentTypeObjectType => UmbracoObjectTypes.MediaType;
|
||||
|
||||
protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.MediaTypeContainer;
|
||||
|
||||
private async Task<IDictionary<IMediaType, IEnumerable<string>>> FetchAllowedFileExtensionsByMediaTypeAsync(IEnumerable<IMediaType> mediaTypes)
|
||||
{
|
||||
var allowedFileExtensionsByMediaType = new Dictionary<IMediaType, IEnumerable<string>>();
|
||||
foreach (IMediaType mediaType in mediaTypes)
|
||||
{
|
||||
IPropertyType? propertyType = mediaType
|
||||
.CompositionPropertyTypes
|
||||
.FirstOrDefault(propertyType => propertyType is
|
||||
{
|
||||
Alias: Constants.Conventions.Media.File,
|
||||
PropertyEditorAlias: Constants.PropertyEditors.Aliases.UploadField
|
||||
});
|
||||
if (propertyType is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IDataType? dataType = await _dataTypeService.GetAsync(propertyType.DataTypeKey);
|
||||
FileUploadConfiguration? fileUploadConfiguration = dataType?.ConfigurationAs<FileUploadConfiguration>();
|
||||
|
||||
if (fileUploadConfiguration is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
allowedFileExtensionsByMediaType[mediaType] = fileUploadConfiguration.FileExtensions;
|
||||
}
|
||||
|
||||
return allowedFileExtensionsByMediaType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
public partial class MediaTypeEditingServiceTests
|
||||
{
|
||||
[TestCase("jpg", Constants.Conventions.MediaTypes.Image)]
|
||||
[TestCase("png", Constants.Conventions.MediaTypes.Image)]
|
||||
[TestCase("svg", Constants.Conventions.MediaTypes.VectorGraphicsAlias)]
|
||||
[TestCase("pdf", Constants.Conventions.MediaTypes.ArticleAlias)]
|
||||
[TestCase("doc", Constants.Conventions.MediaTypes.ArticleAlias)]
|
||||
[TestCase("docx", Constants.Conventions.MediaTypes.ArticleAlias)]
|
||||
[TestCase("mp4", Constants.Conventions.MediaTypes.VideoAlias)]
|
||||
[TestCase("webm", Constants.Conventions.MediaTypes.VideoAlias)]
|
||||
[TestCase("ogv", Constants.Conventions.MediaTypes.VideoAlias)]
|
||||
[TestCase("mp3", Constants.Conventions.MediaTypes.AudioAlias)]
|
||||
[TestCase("weba", Constants.Conventions.MediaTypes.AudioAlias)]
|
||||
[TestCase("oga", Constants.Conventions.MediaTypes.AudioAlias)]
|
||||
[TestCase("opus", Constants.Conventions.MediaTypes.AudioAlias)]
|
||||
[TestCase("abc", Constants.Conventions.MediaTypes.File)]
|
||||
[TestCase("123", Constants.Conventions.MediaTypes.File)]
|
||||
public async Task Can_Get_Default_Allowed_Media_Types(string fileExtension, string expectedMediaTypeAlias)
|
||||
{
|
||||
var allowedMediaTypes = await MediaTypeEditingService.GetMediaTypesForFileExtensionAsync(fileExtension, 0, 100);
|
||||
Assert.AreEqual(1, allowedMediaTypes.Total);
|
||||
Assert.AreEqual(expectedMediaTypeAlias, allowedMediaTypes.Items.First().Alias);
|
||||
}
|
||||
|
||||
[TestCase("jpg")]
|
||||
[TestCase(".jpg")]
|
||||
public async Task Ignores_Heading_Period_For_Allowed_Media_Types(string fileExtension)
|
||||
{
|
||||
var allowedMediaTypes = await MediaTypeEditingService.GetMediaTypesForFileExtensionAsync(fileExtension, 0, 100);
|
||||
Assert.AreEqual(1, allowedMediaTypes.Total);
|
||||
Assert.AreEqual(Constants.Conventions.MediaTypes.Image, allowedMediaTypes.Items.First().Alias);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Yield_Multiple_Allowed_Media_Types()
|
||||
{
|
||||
var mediaType = MediaTypeService.Get(Constants.Conventions.MediaTypes.ArticleAlias)!;
|
||||
var uploadPropertyType = mediaType.PropertyTypes.Single(pt => pt.Alias == Constants.Conventions.Media.File);
|
||||
|
||||
var dataTypeService = GetRequiredService<IDataTypeService>();
|
||||
var dataType = (await dataTypeService.GetAsync(uploadPropertyType.DataTypeKey))!;
|
||||
dataType.ConfigurationData["fileExtensions"] = new[] { "pdf", "jpg" };
|
||||
await dataTypeService.UpdateAsync(dataType, Constants.Security.SuperUserKey);
|
||||
|
||||
var allowedMediaTypes = await MediaTypeEditingService.GetMediaTypesForFileExtensionAsync("jpg", 0, 100);
|
||||
Assert.AreEqual(2, allowedMediaTypes.Total);
|
||||
Assert.AreEqual(Constants.Conventions.MediaTypes.Image, allowedMediaTypes.Items.First().Alias);
|
||||
Assert.AreEqual(Constants.Conventions.MediaTypes.ArticleAlias, allowedMediaTypes.Items.Last().Alias);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Get_Get_Media_Types_For_FileExtensions_Using_Skip_Take()
|
||||
{
|
||||
var mediaType = MediaTypeService.Get(Constants.Conventions.MediaTypes.ArticleAlias)!;
|
||||
var uploadPropertyType = mediaType.PropertyTypes.Single(pt => pt.Alias == Constants.Conventions.Media.File);
|
||||
|
||||
var dataTypeService = GetRequiredService<IDataTypeService>();
|
||||
var dataType = (await dataTypeService.GetAsync(uploadPropertyType.DataTypeKey))!;
|
||||
dataType.ConfigurationData["fileExtensions"] = new[] { "pdf", "jpg" };
|
||||
await dataTypeService.UpdateAsync(dataType, Constants.Security.SuperUserKey);
|
||||
|
||||
var allowedMediaTypes = await MediaTypeEditingService.GetMediaTypesForFileExtensionAsync("jpg", 0, 1);
|
||||
Assert.AreEqual(2, allowedMediaTypes.Total);
|
||||
Assert.AreEqual(1, allowedMediaTypes.Items.Count());
|
||||
Assert.AreEqual(Constants.Conventions.MediaTypes.Image, allowedMediaTypes.Items.First().Alias);
|
||||
|
||||
allowedMediaTypes = await MediaTypeEditingService.GetMediaTypesForFileExtensionAsync("jpg", 1, 1);
|
||||
Assert.AreEqual(2, allowedMediaTypes.Total);
|
||||
Assert.AreEqual(1, allowedMediaTypes.Items.Count());
|
||||
Assert.AreEqual(Constants.Conventions.MediaTypes.ArticleAlias, allowedMediaTypes.Items.First().Alias);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Media;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the media type editing service. Please notice that a lot of functional test is covered by the content type
|
||||
@@ -6,4 +10,16 @@
|
||||
/// </summary>
|
||||
public partial class MediaTypeEditingServiceTests : ContentTypeEditingServiceTestsBase
|
||||
{
|
||||
protected override void ConfigureTestServices(IServiceCollection services)
|
||||
{
|
||||
base.ConfigureTestServices(services);
|
||||
services.AddSingleton<IImageUrlGenerator, TestImageUrlGenerator>();
|
||||
}
|
||||
|
||||
private class TestImageUrlGenerator : IImageUrlGenerator
|
||||
{
|
||||
public IEnumerable<string> SupportedImageFileTypes => new[] { "jpg", "gif", "png" };
|
||||
|
||||
public string? GetImageUrl(ImageUrlGenerationOptions options) => options?.ImageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user