using Newtonsoft.Json.Linq; using System; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Services; using File = System.IO.File; namespace Umbraco.Web.PropertyEditors { /// /// The value editor for the image cropper property editor. /// internal class ImageCropperPropertyValueEditor : DataValueEditor // fixme core vs web? { private readonly ILogger _logger; private readonly MediaFileSystem _mediaFileSystem; public ImageCropperPropertyValueEditor(DataEditorAttribute attribute, ILogger logger, MediaFileSystem mediaFileSystem) : base(attribute) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); } /// /// This is called to merge in the prevalue crops with the value that is saved - similar to the property value converter for the front-end /// public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); if (val == null) return null; ImageCropperValue value; try { value = JsonConvert.DeserializeObject(val.ToString()); } catch { value = new ImageCropperValue { Src = val.ToString() }; } var dataType = dataTypeService.GetDataType(property.PropertyType.DataTypeId); if (dataType?.Configuration != null) value.ApplyConfiguration(dataType.ConfigurationAs()); return value; } /// /// Converts the value received from the editor into the value can be stored in the database. /// /// The value received from the editor. /// The current value of the property /// The converted value. /// /// The is used to re-use the folder, if possible. /// editorValue.Value is used to figure out editorFile and, if it has been cleared, remove the old file - but /// it is editorValue.AdditionalData["files"] that is used to determine the actual file that has been uploaded. /// public override object FromEditor(ContentPropertyData editorValue, object currentValue) { // get the current path var currentPath = string.Empty; try { var svalue = currentValue as string; var currentJson = string.IsNullOrWhiteSpace(svalue) ? null : JObject.Parse(svalue); if (currentJson != null && currentJson["src"] != null) currentPath = currentJson["src"].Value(); } catch (Exception ex) { // for some reason the value is invalid so continue as if there was no value there _logger.Warn(ex, "Could not parse current db value to a JObject."); } if (string.IsNullOrWhiteSpace(currentPath) == false) currentPath = _mediaFileSystem.GetRelativePath(currentPath); // get the new json and path JObject editorJson = null; var editorFile = string.Empty; if (editorValue.Value != null) { editorJson = editorValue.Value as JObject; if (editorJson != null && editorJson["src"] != null) editorFile = editorJson["src"].Value(); } // 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."); // editorFile is empty whenever a new file is being uploaded // or when the file is cleared (in which case editorJson is null) // else editorFile contains the unchanged value 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) { _mediaFileSystem.DeleteFile(currentPath); return null; // clear } return editorJson?.ToString(); // unchanged } // process the file var filepath = editorJson == null ? null : ProcessFile(editorValue, file, currentPath, cuid, puid); // remove all temp files foreach (var f in uploads) File.Delete(f.TempFilePath); // remove current file if replaced if (currentPath != filepath && string.IsNullOrWhiteSpace(currentPath) == false) _mediaFileSystem.DeleteFile(currentPath); // update json and return if (editorJson == null) return null; editorJson["src"] = filepath == null ? string.Empty : _mediaFileSystem.GetUrl(filepath); return editorJson.ToString(); } private string ProcessFile(ContentPropertyData editorValue, ContentPropertyFile file, string currentPath, Guid cuid, Guid puid) { // process the file // no file, invalid file, reject change if (UploadFileTypeValidator.ValidateFileExtension(file.FileName) == false) return null; // get the filepath // in case we are using the old path scheme, try to re-use numbers (bah...) var filepath = _mediaFileSystem.GetMediaPath(file.FileName, currentPath, cuid, puid); // fs-relative path using (var filestream = File.OpenRead(file.TempFilePath)) { _mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite! } return filepath; } public override string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService) { if (value == null || string.IsNullOrEmpty(value.ToString())) return null; // if we dont have a json structure, we will get it from the property type var val = value.ToString(); if (val.DetectIsJson()) return val; // more magic here ;-( var configuration = dataTypeService.GetDataType(propertyType.DataTypeId).ConfigurationAs(); var crops = configuration?.Crops ?? Array.Empty(); return "{src: '" + val + "', crops: " + crops + "}"; } } }