Files
Umbraco-CMS/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs
2018-01-24 13:37:14 +01:00

259 lines
11 KiB
C#

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
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.Media;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
namespace Umbraco.Web.PropertyEditors
{
[ValueEditor(Constants.PropertyEditors.Aliases.ImageCropper, "Image Cropper", "imagecropper", ValueType = ValueTypes.Json, HideLabel = false, Group="media", Icon="icon-crop")]
public class ImageCropperPropertyEditor : PropertyEditor
{
private readonly MediaFileSystem _mediaFileSystem;
private readonly IDataTypeService _dataTypeService;
private readonly UploadAutoFillProperties _autoFillProperties;
private IDictionary<string, object> _internalPreValues; // preValues
/// <summary>
/// Initializes a new instance of the <see cref="ImageCropperPropertyEditor"/> class.
/// </summary>
public ImageCropperPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings, IDataTypeService dataTypeService)
: base(logger)
{
_mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem));
var contentSettings1 = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings));
_dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));
_internalPreValues = new Dictionary<string, object>
{
{"focalPoint", "{left: 0.5, top: 0.5}"},
{"src", ""}
};
_autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, Logger, contentSettings1);
}
public override IDictionary<string, object> DefaultPreValues
{
get => _internalPreValues;
set => _internalPreValues = value;
}
/// <summary>
/// Creates the corresponding property value editor.
/// </summary>
/// <returns>The corresponding property value editor.</returns>
protected override ValueEditor CreateValueEditor()
{
var baseEditor = base.CreateValueEditor();
return new ImageCropperPropertyValueEditor(baseEditor, Logger, _mediaFileSystem);
}
/// <summary>
/// Creates the corresponding preValue editor.
/// </summary>
/// <returns>The corresponding preValue editor.</returns>
protected override ConfigurationEditor CreateConfigurationEditor()
{
return new ImageCropperConfigurationEditor();
}
/// <summary>
/// Gets a value indicating whether a property is an image cropper field.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="ensureValue">A value indicating whether to check that the property has a non-empty value.</param>
/// <returns>A value indicating whether a property is an image cropper field, and (optionaly) has a non-empty value.</returns>
private static bool IsCropperField(Property property, bool ensureValue)
{
if (property.PropertyType.PropertyEditorAlias != Constants.PropertyEditors.Aliases.ImageCropper)
return false;
if (ensureValue == false)
return true;
return property.GetValue() is string && string.IsNullOrWhiteSpace((string) property.GetValue()) == false;
}
/// <summary>
/// Parses the property value into a json object.
/// </summary>
/// <param name="value">The property value.</param>
/// <param name="writeLog">A value indicating whether to log the error.</param>
/// <returns>The json object corresponding to the property value.</returns>
/// <remarks>In case of an error, optionaly logs the error and returns null.</remarks>
private JObject GetJObject(string value, bool writeLog)
{
if (string.IsNullOrWhiteSpace(value))
return null;
try
{
return JsonConvert.DeserializeObject<JObject>(value);
}
catch (Exception ex)
{
if (writeLog)
Logger.Error<ImageCropperPropertyEditor>($"Could not parse image cropper value \"{value}\"", ex);
return null;
}
}
/// <summary>
/// Ensures any files associated are removed
/// </summary>
/// <param name="allPropertyData"></param>
internal IEnumerable<string> ServiceEmptiedRecycleBin(Dictionary<int, IEnumerable<Property>> allPropertyData)
{
return allPropertyData.SelectMany(x => x.Value)
.Where(x => IsCropperField(x, true)).Select(x =>
{
var jo = GetJObject((string) x.GetValue(), true);
if (jo?["src"] == null) return null;
var src = jo["src"].Value<string>();
return string.IsNullOrWhiteSpace(src) ? null : _mediaFileSystem.GetRelativePath(src);
}).WhereNotNull();
}
/// <summary>
/// Ensures any files associated are removed
/// </summary>
/// <param name="deletedEntities"></param>
internal IEnumerable<string> ServiceDeleted(IEnumerable<ContentBase> deletedEntities)
{
return deletedEntities.SelectMany(x => x.Properties)
.Where(x => IsCropperField(x, true)).Select(x =>
{
var jo = GetJObject((string) x.GetValue(), true);
if (jo?["src"] == null) return null;
var src = jo["src"].Value<string>();
return string.IsNullOrWhiteSpace(src) ? null : _mediaFileSystem.GetRelativePath(src);
}).WhereNotNull();
}
/// <summary>
/// After a content has been copied, also copy uploaded files.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs<IContent> args)
{
// get the image cropper field properties with a value
var properties = args.Original.Properties.Where(x => IsCropperField(x, true));
// copy files
var isUpdated = false;
foreach (var property in properties)
{
var jo = GetJObject((string) property.GetValue(), true);
var src = jo?["src"]?.Value<string>();
if (string.IsNullOrWhiteSpace(src)) continue;
var sourcePath = _mediaFileSystem.GetRelativePath(src);
var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath);
jo["src"] = _mediaFileSystem.GetUrl(copyPath);
args.Copy.SetValue(property.Alias, jo.ToString());
isUpdated = true;
}
// if updated, re-save the copy with the updated value
if (isUpdated)
sender.Save(args.Copy);
}
/// <summary>
/// After a media has been created, auto-fill the properties.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs<IMedia> args)
{
AutoFillProperties(args.Entity);
}
/// <summary>
/// After a media has been saved, auto-fill the properties.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs<IMedia> args)
{
foreach (var entity in args.SavedEntities)
AutoFillProperties(entity);
}
/// <summary>
/// After a content item has been saved, auto-fill the properties.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs<IContent> args)
{
foreach (var entity in args.SavedEntities)
AutoFillProperties(entity);
}
/// <summary>
/// Auto-fill properties (or clear).
/// </summary>
private void AutoFillProperties(IContentBase model)
{
var properties = model.Properties.Where(x => IsCropperField(x, false));
foreach (var property in properties)
{
var autoFillConfig = _autoFillProperties.GetConfig(property.Alias);
if (autoFillConfig == null) continue;
foreach (var pvalue in property.Values)
{
var svalue = property.GetValue(pvalue.LanguageId, pvalue.Segment) as string;
if (string.IsNullOrWhiteSpace(svalue))
{
_autoFillProperties.Reset(model, autoFillConfig, pvalue.LanguageId, pvalue.Segment);
continue;
}
// FIXME VERY TEMP
// we should kill all auto-fill properties
// BUT that being said what would be the right way to do this?
/*
var v = JsonConvert.DeserializeObject<ImageCropperValue>()
var jo = GetJObject(svalue, false);
string src;
if (jo == null)
{
// so we have a non-empty string value that cannot be parsed into a json object
// see http://issues.umbraco.org/issue/U4-4756
// it can happen when an image is uploaded via the folder browser, in which case
// the property value will be the file source eg '/media/23454/hello.jpg' and we
// are fixing that anomaly here - does not make any sense at all but... bah...
var config = _dataTypeService
.GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId).FirstOrDefault();
var crops = string.IsNullOrWhiteSpace(config) ? "[]" : config;
src = svalue;
property.SetValue("{\"src\": \"" + svalue + "\", \"crops\": " + crops + "}");
}
else
{
src = jo["src"]?.Value<string>();
}
if (src == null)
_autoFillProperties.Reset(model, autoFillConfig, pvalue.LanguageId, pvalue.Segment);
else
_autoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(src), pvalue.LanguageId, pvalue.Segment);
*/
}
}
}
}
}