using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Web.Media; namespace Umbraco.Web.PropertyEditors { [DataEditor( Constants.PropertyEditors.Aliases.UploadField, "File upload", "fileupload", Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-download-alt")] public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator { private readonly IMediaFileSystem _mediaFileSystem; private readonly IContentSettings _contentSettings; private readonly UploadAutoFillProperties _uploadAutoFillProperties; private readonly IDataTypeService _dataTypeService; private readonly ILocalizationService _localizationService; private readonly ILocalizedTextService _localizedTextService; public FileUploadPropertyEditor(ILogger logger, IMediaFileSystem mediaFileSystem, IContentSettings contentSettings, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper) : base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); _contentSettings = contentSettings; _dataTypeService = dataTypeService; _localizationService = localizationService; _localizedTextService = localizedTextService; _uploadAutoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, contentSettings); } /// /// Creates the corresponding property value editor. /// /// The corresponding property value editor. protected override IDataValueEditor CreateValueEditor() { var editor = new FileUploadPropertyValueEditor(Attribute, _mediaFileSystem, _dataTypeService, _localizationService, _localizedTextService, ShortStringHelper, _contentSettings); editor.Validators.Add(new UploadFileTypeValidator(_localizedTextService, _contentSettings)); return editor; } public bool TryGetMediaPath(string alias, object value, out string mediaPath) { if (alias == Alias) { mediaPath = value?.ToString(); return true; } mediaPath = null; return false; } /// /// Gets a value indicating whether a property is an upload field. /// /// The property. /// A value indicating whether a property is an upload field, and (optionally) has a non-empty value. private static bool IsUploadField(IProperty property) { return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField; } /// /// Ensures any files associated are removed /// /// internal IEnumerable ServiceDeleted(IEnumerable deletedEntities) { return deletedEntities.SelectMany(x => x.Properties) .Where(IsUploadField) .SelectMany(GetFilePathsFromPropertyValues) .Distinct(); } /// /// Look through all property values stored against the property and resolve any file paths stored /// /// /// private IEnumerable GetFilePathsFromPropertyValues(IProperty prop) { var propVals = prop.Values; foreach (var propertyValue in propVals) { //check if the published value contains data and return it var propVal = propertyValue.PublishedValue; if (propVal != null && propVal is string str1 && !str1.IsNullOrWhiteSpace()) yield return _mediaFileSystem.GetRelativePath(str1); //check if the edited value contains data and return it propVal = propertyValue.EditedValue; if (propVal != null && propVal is string str2 && !str2.IsNullOrWhiteSpace()) yield return _mediaFileSystem.GetRelativePath(str2); } } /// /// After a content has been copied, also copy uploaded files. /// /// The event sender. /// The event arguments. internal void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs args) { // get the upload field properties with a value var properties = args.Original.Properties.Where(IsUploadField); // copy files var isUpdated = false; foreach (var property in properties) { //copy each of the property values (variants, segments) to the destination foreach (var propertyValue in property.Values) { var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) continue; var sourcePath = _mediaFileSystem.GetRelativePath(str); var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.Culture, propertyValue.Segment); isUpdated = true; } } // if updated, re-save the copy with the updated value if (isUpdated) sender.Save(args.Copy); } /// /// After a media has been created, auto-fill the properties. /// /// The event sender. /// The event arguments. internal void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs args) { AutoFillProperties(args.Entity); } /// /// After a media has been saved, auto-fill the properties. /// /// The event sender. /// The event arguments. internal void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs args) { foreach (var entity in args.SavedEntities) AutoFillProperties(entity); } /// /// After a content item has been saved, auto-fill the properties. /// /// The event sender. /// The event arguments. internal void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs args) { foreach (var entity in args.SavedEntities) AutoFillProperties(entity); } /// /// Auto-fill properties (or clear). /// private void AutoFillProperties(IContentBase model) { var properties = model.Properties.Where(IsUploadField); foreach (var property in properties) { var autoFillConfig = _contentSettings.GetConfig(property.Alias); if (autoFillConfig == null) continue; foreach (var pvalue in property.Values) { var svalue = property.GetValue(pvalue.Culture, pvalue.Segment) as string; if (string.IsNullOrWhiteSpace(svalue)) _uploadAutoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment); else _uploadAutoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(svalue), pvalue.Culture, pvalue.Segment); } } } } }