Skip to content

Amend accessibility modifiers on file upload property editor components to support extension. #18942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: v16/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ protected override IDataValueEditor CreateValueEditor()
/// <returns>
/// <c>true</c> if the specified property is an upload field; otherwise, <c>false</c>.
/// </returns>
private static bool IsUploadField(IProperty property) => property.PropertyType.PropertyEditorAlias ==
Constants.PropertyEditors.Aliases.UploadField;
protected virtual bool IsUploadField(IProperty property) =>
property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField;

/// <summary>
/// The paths to all file upload property files contained within a collection of content entities
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// The value editor for the file upload property editor.
/// </summary>
internal class FileUploadPropertyValueEditor : DataValueEditor
public class FileUploadPropertyValueEditor : DataValueEditor
{
private readonly MediaFileManager _mediaFileManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly ITemporaryFileService _temporaryFileService;
private readonly IScopeProvider _scopeProvider;
private readonly IFileStreamSecurityValidator _fileStreamSecurityValidator;
private ContentSettings _contentSettings;

/// <summary>
/// Initializes a new instance of the <see cref="FileUploadPropertyValueEditor"/> class.
/// </summary>
public FileUploadPropertyValueEditor(
DataEditorAttribute attribute,
MediaFileManager mediaFileManager,
Expand All @@ -41,21 +41,39 @@ public FileUploadPropertyValueEditor(
IFileStreamSecurityValidator fileStreamSecurityValidator)
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager));
_jsonSerializer = jsonSerializer;
_temporaryFileService = temporaryFileService;
_scopeProvider = scopeProvider;
_fileStreamSecurityValidator = fileStreamSecurityValidator;
_contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings));
contentSettings.OnChange(x => _contentSettings = x);

MediaFileManager = mediaFileManager;
FileStreamSecurityValidator = fileStreamSecurityValidator;

ContentSettings = contentSettings.CurrentValue;
contentSettings.OnChange(x => ContentSettings = x);

Validators.Add(new TemporaryFileUploadValidator(
() => _contentSettings,
() => ContentSettings,
TryParseTemporaryFileKey,
TryGetTemporaryFile,
IsAllowedInDataTypeConfiguration));
}

/// <summary>
/// Gets the <see cref="MediaFileManager"/>.
/// </summary>
protected MediaFileManager MediaFileManager { get; }

/// <summary>
/// Gets the <see cref="IFileStreamSecurityValidator"/>.
/// </summary>
protected IFileStreamSecurityValidator FileStreamSecurityValidator { get; }

/// <summary>
/// Gets the <see cref="ContentSettings"/>.
/// </summary>
protected ContentSettings ContentSettings { get; private set; }

/// <inheritdoc/>
public override object? ToEditor(IProperty property, string? culture = null, string? segment = null)
{
// the stored property value (if any) is the path to the file; convert it to the client model (FileUploadValue)
Expand All @@ -68,6 +86,7 @@ public FileUploadPropertyValueEditor(
: null;
}

/// <inheritdoc/>
/// <summary>
/// Converts the client model (FileUploadValue) into the value can be stored in the database (the file path).
/// </summary>
Expand All @@ -94,14 +113,14 @@ public FileUploadPropertyValueEditor(
// the current editor value (if any) is the path to the file
var currentPath = currentValue is string currentStringValue
&& currentStringValue.IsNullOrWhiteSpace() is false
? _mediaFileManager.FileSystem.GetRelativePath(currentStringValue)
? MediaFileManager.FileSystem.GetRelativePath(currentStringValue)
: null;

// resetting the current value?
if (string.IsNullOrEmpty(editorModelValue?.Src) && currentPath.IsNullOrWhiteSpace() is false)
{
// delete the current file and clear the value of this property
_mediaFileManager.FileSystem.DeleteFile(currentPath);
MediaFileManager.FileSystem.DeleteFile(currentPath);
return null;
}

Expand Down Expand Up @@ -138,12 +157,12 @@ public FileUploadPropertyValueEditor(
// remove current file if replaced
if (currentPath != filepath && currentPath.IsNullOrWhiteSpace() is false)
{
_mediaFileManager.FileSystem.DeleteFile(currentPath);
MediaFileManager.FileSystem.DeleteFile(currentPath);
}

scope.Complete();

return filepath is null ? null : _mediaFileManager.FileSystem.GetUrl(filepath);
return filepath is null ? null : MediaFileManager.FileSystem.GetUrl(filepath);
}

private FileUploadValue? ParseFileUploadValue(object? editorValue)
Expand Down Expand Up @@ -192,26 +211,26 @@ private bool IsAllowedInDataTypeConfiguration(string extension, object? dataType
// this check is somewhat redundant as the file validity has already been checked by TemporaryFileUploadValidator,
// but we'll retain it here as a last measure in case someone accidentally breaks the validator
var extension = Path.GetExtension(file.FileName).TrimStart('.');
if (_contentSettings.IsFileAllowedForUpload(extension) is false ||
if (ContentSettings.IsFileAllowedForUpload(extension) is false ||
IsAllowedInDataTypeConfiguration(extension, dataTypeConfiguration) is false)
{
return null;
}

// get the filepath
// in case we are using the old path scheme, try to re-use numbers (bah...)
var filepath = _mediaFileManager.GetMediaPath(file.FileName, contentKey, propertyTypeKey); // fs-relative path
var filepath = MediaFileManager.GetMediaPath(file.FileName, contentKey, propertyTypeKey); // fs-relative path

using (Stream filestream = file.OpenReadStream())
{
if (_fileStreamSecurityValidator.IsConsideredSafe(filestream) == false)
if (FileStreamSecurityValidator.IsConsideredSafe(filestream) == false)
{
return null;
}

// TODO: Here it would make sense to do the auto-fill properties stuff but the API doesn't allow us to do that right
// since we'd need to be able to return values for other properties from these methods
_mediaFileManager.FileSystem.AddFile(filepath, filestream, true); // must overwrite!
MediaFileManager.FileSystem.AddFile(filepath, filestream, true); // must overwrite!
}

return filepath;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models.TemporaryFile;
using Umbraco.Cms.Core.Models.Validation;
using Umbraco.Extensions;

namespace Umbraco.Cms.Core.PropertyEditors;

internal class TemporaryFileUploadValidator : IValueValidator
/// <summary>
/// Validates a temporary file upload.
/// </summary>
public class TemporaryFileUploadValidator : IValueValidator
{
private readonly GetContentSettings _getContentSettings;
private readonly ParseTemporaryFileKey _parseTemporaryFileKey;
private readonly GetTemporaryFileModel _getTemporaryFileModel;
private readonly ValidateFileType? _validateFileType;

internal delegate ContentSettings GetContentSettings();
public delegate ContentSettings GetContentSettings();

internal delegate Guid? ParseTemporaryFileKey(object? editorValue);
public delegate Guid? ParseTemporaryFileKey(object? editorValue);

internal delegate TemporaryFileModel? GetTemporaryFileModel(Guid temporaryFileKey);
public delegate TemporaryFileModel? GetTemporaryFileModel(Guid temporaryFileKey);

internal delegate bool ValidateFileType(string extension, object? dataTypeConfiguration);
public delegate bool ValidateFileType(string extension, object? dataTypeConfiguration);

/// <summary>
/// Initializes a new instance of the <see cref="TemporaryFileUploadValidator"/> class.
/// </summary>
public TemporaryFileUploadValidator(
GetContentSettings getContentSettings,
ParseTemporaryFileKey parseTemporaryFileKey,
Expand All @@ -33,6 +39,7 @@ public TemporaryFileUploadValidator(
_validateFileType = validateFileType;
}

/// <inheritdoc/>
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
{
Guid? temporaryFileKey = _parseTemporaryFileKey(value);
Expand All @@ -46,7 +53,7 @@ public IEnumerable<ValidationResult> Validate(object? value, string? valueType,
{
yield return new ValidationResult(
$"No temporary file was found for the key: {temporaryFileKey.Value}",
new[] { "value" });
["value"]);
}
else
{
Expand All @@ -61,7 +68,7 @@ public IEnumerable<ValidationResult> Validate(object? value, string? valueType,
{
yield return new ValidationResult(
$"The file type for file name \"{temporaryFile.FileName}\" is not valid for upload",
new[] { "value" });
["value"]);
}
}
}
Expand Down