Skip to content

Making FileUploadPropertyEditor more flexible #18336

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

Closed
wants to merge 4 commits into from
Closed
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,7 @@ 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 == Alias;

/// <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,14 +20,14 @@ 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;
protected MediaFileManager MediaFileManager { get; }
protected IFileStreamSecurityValidator FileStreamSecurityValidator { get; }
protected ContentSettings ContentSettings { get; private set; }

public FileUploadPropertyValueEditor(
DataEditorAttribute attribute,
Expand All @@ -42,16 +42,16 @@ public FileUploadPropertyValueEditor(
IFileStreamSecurityValidator fileStreamSecurityValidator)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager));
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);
FileStreamSecurityValidator = fileStreamSecurityValidator;
ContentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings));
contentSettings.OnChange(x => ContentSettings = x);

Validators.Add(new TemporaryFileUploadValidator(
() => _contentSettings,
() => ContentSettings,
TryParseTemporaryFileKey,
TryGetTemporaryFile,
IsAllowedInDataTypeConfiguration));
Expand Down Expand Up @@ -95,14 +95,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 @@ -139,12 +139,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 @@ -173,7 +173,7 @@ public FileUploadPropertyValueEditor(
private TemporaryFileModel? TryGetTemporaryFile(Guid temporaryFileKey)
=> _temporaryFileService.GetAsync(temporaryFileKey).GetAwaiter().GetResult();

private bool IsAllowedInDataTypeConfiguration(string extension, object? dataTypeConfiguration)
protected virtual bool IsAllowedInDataTypeConfiguration(string extension, object? dataTypeConfiguration)
{
if (dataTypeConfiguration is FileUploadConfiguration fileUploadConfiguration)
{
Expand All @@ -186,33 +186,33 @@ private bool IsAllowedInDataTypeConfiguration(string extension, object? dataType
return false;
}

private string? ProcessFile(TemporaryFileModel file, object? dataTypeConfiguration, Guid contentKey, Guid propertyTypeKey)
protected virtual string? ProcessFile(TemporaryFileModel file, object? dataTypeConfiguration, Guid contentKey, Guid propertyTypeKey)
{
// process the file
// no file, invalid file, reject change
// 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,25 +1,25 @@
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
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);

public TemporaryFileUploadValidator(
GetContentSettings getContentSettings,
Expand Down
Loading