Files
Umbraco-CMS/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs
2021-09-07 12:10:58 +02:00

140 lines
6.2 KiB
C#

// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.IO;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models.Editors;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors
{
/// <summary>
/// The value editor for the file upload property editor.
/// </summary>
internal class FileUploadPropertyValueEditor : DataValueEditor
{
private readonly MediaFileManager _mediaFileManager;
private readonly ContentSettings _contentSettings;
public FileUploadPropertyValueEditor(
DataEditorAttribute attribute,
MediaFileManager mediaFileManager,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
IOptions<ContentSettings> contentSettings,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager));
_contentSettings = contentSettings.Value ?? throw new ArgumentNullException(nameof(contentSettings));
}
/// <summary>
/// Converts the value received from the editor into the value can be stored in the database.
/// </summary>
/// <param name="editorValue">The value received from the editor.</param>
/// <param name="currentValue">The current value of the property</param>
/// <returns>The converted value.</returns>
/// <remarks>
/// <para>The <paramref name="currentValue"/> is used to re-use the folder, if possible.</para>
/// <para>The <paramref name="editorValue"/> is value passed in from the editor. We normally don't care what
/// the editorValue.Value is set to because we are more interested in the files collection associated with it,
/// however we do care about the value if we are clearing files. By default the editorValue.Value will just
/// be set to the name of the file - but again, we just ignore this and deal with the file collection in
/// editorValue.AdditionalData.ContainsKey("files")</para>
/// <para>We only process ONE file. We understand that the current value may contain more than one file,
/// and that more than one file may be uploaded, so we take care of them all, but we only store ONE file.
/// Other places (FileUploadPropertyEditor...) do NOT deal with multiple files, and our logic for reusing
/// folders would NOT work, etc.</para>
/// </remarks>
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
{
var currentPath = currentValue as string;
if (!currentPath.IsNullOrWhiteSpace())
currentPath = _mediaFileManager.FileSystem.GetRelativePath(currentPath);
string editorFile = null;
if (editorValue.Value != null)
{
editorFile = editorValue.Value as string;
}
// ensure we have the required guids
var cuid = editorValue.ContentKey;
if (cuid == Guid.Empty) throw new Exception("Invalid content key.");
var puid = editorValue.PropertyTypeKey;
if (puid == Guid.Empty) throw new Exception("Invalid property type key.");
var uploads = editorValue.Files;
if (uploads == null) throw new Exception("Invalid files.");
var file = uploads.Length > 0 ? uploads[0] : null;
if (file == null) // not uploading a file
{
// if editorFile is empty then either there was nothing to begin with,
// or it has been cleared and we need to remove the file - else the
// value is unchanged.
if (string.IsNullOrWhiteSpace(editorFile) && string.IsNullOrWhiteSpace(currentPath) == false)
{
_mediaFileManager.FileSystem.DeleteFile(currentPath);
return null; // clear
}
return currentValue; // unchanged
}
// process the file
var filepath = editorFile == null ? null : ProcessFile(file, editorValue.DataTypeConfiguration, cuid, puid);
// remove all temp files
foreach (ContentPropertyFile f in uploads)
{
File.Delete(f.TempFilePath);
}
// remove current file if replaced
if (currentPath != filepath && string.IsNullOrWhiteSpace(currentPath) == false)
_mediaFileManager.FileSystem.DeleteFile(currentPath);
// update json and return
if (editorFile == null) return null;
return filepath == null ? string.Empty : _mediaFileManager.FileSystem.GetUrl(filepath);
}
private string ProcessFile(ContentPropertyFile file, object dataTypeConfiguration, Guid cuid, Guid puid)
{
// process the file
// no file, invalid file, reject change
if (UploadFileTypeValidator.IsValidFileExtension(file.FileName, _contentSettings) is false ||
UploadFileTypeValidator.IsAllowedInDataTypeConfiguration(file.FileName, 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, cuid, puid); // fs-relative path
using (var filestream = File.OpenRead(file.TempFilePath))
{
// 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!
}
return filepath;
}
}
}