2013-11-07 17:16:22 +01:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
|
|
using System.Drawing;
|
2014-08-22 18:30:32 +02:00
|
|
|
using System.Globalization;
|
2013-11-07 17:16:22 +01:00
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Xml;
|
2014-09-10 15:07:20 +10:00
|
|
|
using Newtonsoft.Json;
|
2013-11-07 17:16:22 +01:00
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
using Umbraco.Core;
|
|
|
|
|
using Umbraco.Core.Configuration;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Web.PropertyEditors
|
|
|
|
|
{
|
2015-06-24 21:43:14 +02:00
|
|
|
[PropertyEditor(Constants.PropertyEditors.UploadFieldAlias, "File upload", "fileupload", Icon = "icon-download-alt", Group = "media")]
|
2015-01-23 20:00:44 +11:00
|
|
|
public class FileUploadPropertyEditor : PropertyEditor, IApplicationEventHandler
|
2013-11-07 17:16:22 +01:00
|
|
|
{
|
2015-01-23 20:00:44 +11:00
|
|
|
private readonly MediaFileSystem _mediaFileSystem;
|
|
|
|
|
private readonly IContentSection _contentSettings;
|
2016-04-21 11:38:43 +02:00
|
|
|
private readonly ILocalizedTextService _textService;
|
2015-01-23 20:00:44 +11:00
|
|
|
|
2016-04-21 11:38:43 +02:00
|
|
|
public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings, ILocalizedTextService textService)
|
2015-01-23 20:00:44 +11:00
|
|
|
: base(logger)
|
2013-11-07 17:16:22 +01:00
|
|
|
{
|
2015-01-23 20:00:44 +11:00
|
|
|
if (mediaFileSystem == null) throw new ArgumentNullException("mediaFileSystem");
|
|
|
|
|
if (contentSettings == null) throw new ArgumentNullException("contentSettings");
|
2016-04-21 11:38:43 +02:00
|
|
|
if (textService == null) throw new ArgumentNullException("textService");
|
2016-06-01 12:20:12 +02:00
|
|
|
_applicationStartup = new FileUploadPropertyEditorApplicationStartup(this);
|
2015-01-23 20:00:44 +11:00
|
|
|
_mediaFileSystem = mediaFileSystem;
|
|
|
|
|
_contentSettings = contentSettings;
|
2016-04-21 11:38:43 +02:00
|
|
|
_textService = textService;
|
2016-06-01 12:20:12 +02:00
|
|
|
|
2013-11-07 17:16:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates our custom value editor
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
protected override PropertyValueEditor CreateValueEditor()
|
|
|
|
|
{
|
|
|
|
|
var baseEditor = base.CreateValueEditor();
|
|
|
|
|
baseEditor.Validators.Add(new UploadFileTypeValidator());
|
2015-01-23 20:00:44 +11:00
|
|
|
return new FileUploadPropertyValueEditor(baseEditor, _mediaFileSystem, _contentSettings);
|
2013-11-07 17:16:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override PreValueEditor CreatePreValueEditor()
|
|
|
|
|
{
|
2016-04-21 11:38:43 +02:00
|
|
|
return new FileUploadPreValueEditor(_textService);
|
2013-11-07 17:16:22 +01:00
|
|
|
}
|
|
|
|
|
|
2014-09-10 15:07:20 +10:00
|
|
|
/// <summary>
|
|
|
|
|
/// Ensures any files associated are removed
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="allPropertyData"></param>
|
2016-06-01 12:20:12 +02:00
|
|
|
IEnumerable<string> ServiceEmptiedRecycleBin(Dictionary<int, IEnumerable<Property>> allPropertyData)
|
2014-09-10 15:07:20 +10:00
|
|
|
{
|
|
|
|
|
var list = new List<string>();
|
|
|
|
|
//Get all values for any image croppers found
|
|
|
|
|
foreach (var uploadVal in allPropertyData
|
|
|
|
|
.SelectMany(x => x.Value)
|
|
|
|
|
.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
|
|
|
|
|
.Select(x => x.Value)
|
|
|
|
|
.WhereNotNull())
|
|
|
|
|
{
|
|
|
|
|
if (uploadVal.ToString().IsNullOrWhiteSpace() == false)
|
|
|
|
|
{
|
|
|
|
|
list.Add(uploadVal.ToString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Ensures any files associated are removed
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="deletedEntities"></param>
|
2016-06-01 12:20:12 +02:00
|
|
|
IEnumerable<string> ServiceDeleted(IEnumerable<ContentBase> deletedEntities)
|
2014-09-10 15:07:20 +10:00
|
|
|
{
|
|
|
|
|
var list = new List<string>();
|
|
|
|
|
foreach (var property in deletedEntities.SelectMany(deletedEntity => deletedEntity
|
|
|
|
|
.Properties
|
|
|
|
|
.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias
|
|
|
|
|
&& x.Value != null
|
|
|
|
|
&& string.IsNullOrEmpty(x.Value.ToString()) == false)))
|
|
|
|
|
{
|
|
|
|
|
if (property.Value != null && property.Value.ToString().IsNullOrWhiteSpace() == false)
|
|
|
|
|
{
|
|
|
|
|
list.Add(property.Value.ToString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-06 15:27:53 +10:00
|
|
|
/// <summary>
|
|
|
|
|
/// After the content is copied we need to check if there are files that also need to be copied
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="sender"></param>
|
|
|
|
|
/// <param name="e"></param>
|
2015-01-23 20:00:44 +11:00
|
|
|
void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs<IContent> e)
|
2014-05-06 15:27:53 +10:00
|
|
|
{
|
|
|
|
|
if (e.Original.Properties.Any(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias))
|
|
|
|
|
{
|
|
|
|
|
bool isUpdated = false;
|
2015-01-23 20:00:44 +11:00
|
|
|
var fs = _mediaFileSystem;
|
2014-05-06 15:27:53 +10:00
|
|
|
|
|
|
|
|
//Loop through properties to check if the content contains media that should be deleted
|
|
|
|
|
foreach (var property in e.Original.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias
|
|
|
|
|
&& x.Value != null
|
|
|
|
|
&& string.IsNullOrEmpty(x.Value.ToString()) == false))
|
|
|
|
|
{
|
2014-06-27 17:22:51 +02:00
|
|
|
if (fs.FileExists(fs.GetRelativePath(property.Value.ToString())))
|
2014-05-06 15:27:53 +10:00
|
|
|
{
|
|
|
|
|
var currentPath = fs.GetRelativePath(property.Value.ToString());
|
|
|
|
|
var propertyId = e.Copy.Properties.First(x => x.Alias == property.Alias).Id;
|
|
|
|
|
var newPath = fs.GetRelativePath(propertyId, System.IO.Path.GetFileName(currentPath));
|
|
|
|
|
|
|
|
|
|
fs.CopyFile(currentPath, newPath);
|
|
|
|
|
e.Copy.SetValue(property.Alias, fs.GetUrl(newPath));
|
|
|
|
|
|
|
|
|
|
//Copy thumbnails
|
|
|
|
|
foreach (var thumbPath in fs.GetThumbnails(currentPath))
|
|
|
|
|
{
|
|
|
|
|
var newThumbPath = fs.GetRelativePath(propertyId, System.IO.Path.GetFileName(thumbPath));
|
|
|
|
|
fs.CopyFile(thumbPath, newThumbPath);
|
|
|
|
|
}
|
|
|
|
|
isUpdated = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isUpdated)
|
|
|
|
|
{
|
|
|
|
|
//need to re-save the copy with the updated path value
|
|
|
|
|
sender.Save(e.Copy);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-23 20:00:44 +11:00
|
|
|
void MediaServiceCreating(IMediaService sender, Core.Events.NewEventArgs<IMedia> e)
|
2013-11-07 17:16:22 +01:00
|
|
|
{
|
|
|
|
|
AutoFillProperties(e.Entity);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-23 20:00:44 +11:00
|
|
|
void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs<IMedia> e)
|
2013-11-07 17:16:22 +01:00
|
|
|
{
|
|
|
|
|
foreach (var m in e.SavedEntities)
|
|
|
|
|
{
|
|
|
|
|
AutoFillProperties(m);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-23 20:00:44 +11:00
|
|
|
void AutoFillProperties(IContentBase model)
|
2013-12-04 19:47:54 +01:00
|
|
|
{
|
2014-03-18 22:31:05 +01:00
|
|
|
foreach (var p in model.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias))
|
2013-11-07 17:16:22 +01:00
|
|
|
{
|
|
|
|
|
var uploadFieldConfigNode =
|
2015-01-23 20:00:44 +11:00
|
|
|
_contentSettings.ImageAutoFillProperties
|
2013-11-07 17:16:22 +01:00
|
|
|
.FirstOrDefault(x => x.Alias == p.Alias);
|
|
|
|
|
|
|
|
|
|
if (uploadFieldConfigNode != null)
|
|
|
|
|
{
|
2014-03-19 15:32:31 +11:00
|
|
|
model.PopulateFileMetaDataProperties(uploadFieldConfigNode, p.Value == null ? string.Empty : p.Value.ToString());
|
2013-11-07 17:16:22 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// A custom pre-val editor to ensure that the data is stored how the legacy data was stored in
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class FileUploadPreValueEditor : ValueListPreValueEditor
|
|
|
|
|
{
|
2016-04-21 11:38:43 +02:00
|
|
|
public FileUploadPreValueEditor(ILocalizedTextService textService)
|
|
|
|
|
: base(textService)
|
2013-11-07 17:16:22 +01:00
|
|
|
{
|
|
|
|
|
var field = Fields.First();
|
|
|
|
|
field.Description = "Enter a max width/height for each thumbnail";
|
|
|
|
|
field.Name = "Add thumbnail size";
|
|
|
|
|
//need to have some custom validation happening here
|
|
|
|
|
field.Validators.Add(new ThumbnailListValidator());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Format the persisted value to work with our multi-val editor.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="defaultPreVals"></param>
|
|
|
|
|
/// <param name="persistedPreVals"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public override IDictionary<string, object> ConvertDbToEditor(IDictionary<string, object> defaultPreVals, PreValueCollection persistedPreVals)
|
|
|
|
|
{
|
2014-08-22 18:30:32 +02:00
|
|
|
var result = new List<PreValue>();
|
2013-11-07 17:16:22 +01:00
|
|
|
|
|
|
|
|
//the pre-values just take up one field with a semi-colon delimiter so we'll just parse
|
|
|
|
|
var dictionary = persistedPreVals.FormatAsDictionary();
|
|
|
|
|
if (dictionary.Any())
|
|
|
|
|
{
|
|
|
|
|
//there should only be one val
|
2014-08-22 18:30:32 +02:00
|
|
|
var delimited = dictionary.First().Value.Value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
2013-11-07 17:16:22 +01:00
|
|
|
for (var index = 0; index < delimited.Length; index++)
|
|
|
|
|
{
|
2014-08-22 18:30:32 +02:00
|
|
|
result.Add(new PreValue(index, delimited[index]));
|
2013-11-07 17:16:22 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//the items list will be a dictionary of it's id -> value we need to use the id for persistence for backwards compatibility
|
2014-08-22 18:30:32 +02:00
|
|
|
return new Dictionary<string, object> { { "items", result.ToDictionary(x => x.Id, x => PreValueAsDictionary(x)) } };
|
2013-11-07 17:16:22 +01:00
|
|
|
}
|
|
|
|
|
|
2014-08-22 18:30:32 +02:00
|
|
|
private IDictionary<string, object> PreValueAsDictionary(PreValue preValue)
|
|
|
|
|
{
|
|
|
|
|
return new Dictionary<string, object> { { "value", preValue.Value }, { "sortOrder", preValue.SortOrder } };
|
|
|
|
|
}
|
2013-11-07 17:16:22 +01:00
|
|
|
/// <summary>
|
|
|
|
|
/// Take the posted values and convert them to a semi-colon separated list so that its backwards compatible
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="editorValue"></param>
|
|
|
|
|
/// <param name="currentValue"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public override IDictionary<string, PreValue> ConvertEditorToDb(IDictionary<string, object> editorValue, PreValueCollection currentValue)
|
|
|
|
|
{
|
|
|
|
|
var result = base.ConvertEditorToDb(editorValue, currentValue);
|
|
|
|
|
|
|
|
|
|
//this should just be a dictionary of values, we want to re-format this so that it is just one value in the dictionary that is
|
|
|
|
|
// semi-colon delimited
|
|
|
|
|
var values = result.Select(item => item.Value.Value).ToList();
|
|
|
|
|
|
|
|
|
|
result.Clear();
|
|
|
|
|
result.Add("thumbs", new PreValue(string.Join(";", values)));
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal class ThumbnailListValidator : IPropertyValidator
|
|
|
|
|
{
|
|
|
|
|
public IEnumerable<ValidationResult> Validate(object value, PreValueCollection preValues, PropertyEditor editor)
|
|
|
|
|
{
|
|
|
|
|
var json = value as JArray;
|
|
|
|
|
if (json == null) yield break;
|
|
|
|
|
|
|
|
|
|
//validate each item which is a json object
|
|
|
|
|
for (var index = 0; index < json.Count; index++)
|
|
|
|
|
{
|
|
|
|
|
var i = json[index];
|
|
|
|
|
var jItem = i as JObject;
|
|
|
|
|
if (jItem == null || jItem["value"] == null) continue;
|
|
|
|
|
|
|
|
|
|
//NOTE: we will be removing empty values when persisting so no need to validate
|
|
|
|
|
var asString = jItem["value"].ToString();
|
|
|
|
|
if (asString.IsNullOrWhiteSpace()) continue;
|
|
|
|
|
|
|
|
|
|
int parsed;
|
|
|
|
|
if (int.TryParse(asString, out parsed) == false)
|
|
|
|
|
{
|
|
|
|
|
yield return new ValidationResult("The value " + asString + " is not a valid number", new[]
|
|
|
|
|
{
|
|
|
|
|
//we'll make the server field the index number of the value so it can be wired up to the view
|
|
|
|
|
"item_" + index.ToInvariantString()
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-23 20:00:44 +11:00
|
|
|
#region Application event handler, used to bind to events on startup
|
2016-06-01 11:27:08 +02:00
|
|
|
|
2016-06-01 12:20:12 +02:00
|
|
|
private readonly FileUploadPropertyEditorApplicationStartup _applicationStartup;
|
2016-06-01 11:27:08 +02:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// we're using a sub -class because this has the logic to prevent it from executing if the application is not configured
|
|
|
|
|
/// </summary>
|
|
|
|
|
private class FileUploadPropertyEditorApplicationStartup : ApplicationEventHandler
|
2015-01-23 20:00:44 +11:00
|
|
|
{
|
2016-06-01 12:20:12 +02:00
|
|
|
private FileUploadPropertyEditor _fileUploadPropertyEditor;
|
|
|
|
|
|
|
|
|
|
public FileUploadPropertyEditorApplicationStartup(FileUploadPropertyEditor fileUploadPropertyEditor)
|
|
|
|
|
{
|
|
|
|
|
this._fileUploadPropertyEditor = fileUploadPropertyEditor;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-01 11:27:08 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// We're going to bind to the MediaService Saving event so that we can populate the umbracoFile size, type, etc... label fields
|
|
|
|
|
/// if we find any attached to the current media item.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
|
|
|
|
|
{
|
2016-06-01 12:20:12 +02:00
|
|
|
MediaService.Saving += _fileUploadPropertyEditor.MediaServiceSaving;
|
|
|
|
|
MediaService.Created += _fileUploadPropertyEditor.MediaServiceCreating;
|
|
|
|
|
ContentService.Copied += _fileUploadPropertyEditor.ContentServiceCopied;
|
2016-06-01 11:27:08 +02:00
|
|
|
|
|
|
|
|
MediaService.Deleted += (sender, args) =>
|
2016-06-01 12:20:12 +02:00
|
|
|
args.MediaFilesToDelete.AddRange(_fileUploadPropertyEditor.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
2016-06-01 11:27:08 +02:00
|
|
|
MediaService.EmptiedRecycleBin += (sender, args) =>
|
2016-06-01 12:20:12 +02:00
|
|
|
args.Files.AddRange(_fileUploadPropertyEditor.ServiceEmptiedRecycleBin(args.AllPropertyData));
|
2016-06-01 11:27:08 +02:00
|
|
|
ContentService.Deleted += (sender, args) =>
|
2016-06-01 12:20:12 +02:00
|
|
|
args.MediaFilesToDelete.AddRange(_fileUploadPropertyEditor.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
2016-06-01 11:27:08 +02:00
|
|
|
ContentService.EmptiedRecycleBin += (sender, args) =>
|
2016-06-01 12:20:12 +02:00
|
|
|
args.Files.AddRange(_fileUploadPropertyEditor.ServiceEmptiedRecycleBin(args.AllPropertyData));
|
2016-06-01 11:27:08 +02:00
|
|
|
MemberService.Deleted += (sender, args) =>
|
2016-06-01 12:20:12 +02:00
|
|
|
args.MediaFilesToDelete.AddRange(_fileUploadPropertyEditor.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
2016-06-01 11:27:08 +02:00
|
|
|
}
|
2015-01-23 20:00:44 +11:00
|
|
|
}
|
|
|
|
|
|
2016-06-01 11:27:08 +02:00
|
|
|
public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
|
|
|
|
|
{
|
|
|
|
|
//wrap
|
|
|
|
|
_applicationStartup.OnApplicationInitialized(umbracoApplication, applicationContext);
|
|
|
|
|
}
|
2015-01-23 20:00:44 +11:00
|
|
|
public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
|
|
|
|
|
{
|
2016-06-01 11:27:08 +02:00
|
|
|
//wrap
|
|
|
|
|
_applicationStartup.OnApplicationStarting(umbracoApplication, applicationContext);
|
2015-01-23 20:00:44 +11:00
|
|
|
}
|
|
|
|
|
public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
|
|
|
|
|
{
|
2016-06-01 11:27:08 +02:00
|
|
|
//wrap
|
|
|
|
|
_applicationStartup.OnApplicationStarted(umbracoApplication, applicationContext);
|
|
|
|
|
}
|
2015-01-23 20:00:44 +11:00
|
|
|
#endregion
|
2016-06-01 11:27:08 +02:00
|
|
|
|
2013-11-07 17:16:22 +01:00
|
|
|
}
|
|
|
|
|
}
|