diff --git a/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs b/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs index f4ace38465..6af13d6887 100644 --- a/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs +++ b/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs @@ -64,7 +64,7 @@ namespace Umbraco.Core.Dynamics /// public class DynamicXmlConverter : TypeConverter { - public override bool CanConvertTo(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { var convertableTypes = new[] { @@ -78,8 +78,8 @@ namespace Umbraco.Core.Dynamics typeof(RawXmlDocument) }; - return convertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, sourceType)) - || base.CanConvertFrom(context, sourceType); + return convertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, destinationType)) + || base.CanConvertFrom(context, destinationType); } public override object ConvertTo( diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs index 8ad0b7d69d..fac6da202f 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -1,8 +1,10 @@ using System; +using System.Globalization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; namespace Umbraco.Core.PropertyEditors.ValueConverters { @@ -14,6 +16,19 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] public class ImageCropperValueConverter : JsonValueConverter { + private readonly IDataTypeService _dataTypeService; + + public ImageCropperValueConverter() + { + _dataTypeService = ApplicationContext.Current.Services.DataTypeService; + } + + public ImageCropperValueConverter(IDataTypeService dataTypeService) + { + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + _dataTypeService = dataTypeService; + } + public override bool IsConverter(PublishedPropertyType propertyType) { return propertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.ImageCropperAlias); @@ -29,7 +44,11 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters JObject obj; try { - obj = JsonConvert.DeserializeObject(sourceString); + obj = JsonConvert.DeserializeObject(sourceString, new JsonSerializerSettings + { + Culture = CultureInfo.InvariantCulture, + FloatParseHandling = FloatParseHandling.Decimal + }); } catch (Exception ex) { @@ -39,7 +58,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters //need to lookup the pre-values for this data type //TODO: Change all singleton access to use ctor injection in v8!!! - var dt = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); + var dt = _dataTypeService.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); if (dt != null && dt.IsDictionaryBased && dt.PreValuesAsDictionary.ContainsKey("crops")) { diff --git a/src/Umbraco.Core/Serialization/NoTypeConverterJsonConverter.cs b/src/Umbraco.Core/Serialization/NoTypeConverterJsonConverter.cs new file mode 100644 index 0000000000..78dda6c4c6 --- /dev/null +++ b/src/Umbraco.Core/Serialization/NoTypeConverterJsonConverter.cs @@ -0,0 +1,52 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Umbraco.Core.Serialization +{ + /// + /// This is required if we want to force JSON.Net to not use .Net TypeConverters during serialization/deserialization + /// + /// + /// + /// In some cases thsi is required if your model has an explicit type converter, see: http://stackoverflow.com/a/31328131/694494 + /// + /// NOTE: I was going to use this for the ImageCropDataSetConverter to convert to String, which would have worked by putting this attribute: + /// [JsonConverter(typeof(NoTypeConverterJsonConverter{ImageCropDataSet}))] on top of the ImageCropDataSet class, however it turns out we + /// don't require this because to convert to string, we just override ToString(). + /// I'll leave this class here for the future though. + /// + internal class NoTypeConverterJsonConverter : JsonConverter + { + static readonly IContractResolver resolver = new NoTypeConverterContractResolver(); + + private class NoTypeConverterContractResolver : DefaultContractResolver + { + protected override JsonContract CreateContract(Type objectType) + { + if (typeof(T).IsAssignableFrom(objectType)) + { + var contract = this.CreateObjectContract(objectType); + contract.Converter = null; // Also null out the converter to prevent infinite recursion. + return contract; + } + return base.CreateContract(objectType); + } + } + + public override bool CanConvert(Type objectType) + { + return typeof(T).IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Deserialize(reader, objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Serialize(writer, value); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4378b29586..ba14fa044a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -489,6 +489,7 @@ + diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 2877e3bb90..41ba665b4a 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -1,6 +1,16 @@ -using System.Linq; +using System; +using System.Linq; +using Moq; +using Newtonsoft.Json; using NUnit.Framework; using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.PropertyEditors; using Umbraco.Web; @@ -10,14 +20,74 @@ namespace Umbraco.Tests.PropertyEditors [TestFixture] public class ImageCropperTest { - private const string cropperJson = "{\"focalPoint\": {\"left\": 0.96,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}"; - + private const string cropperJson1 = "{\"focalPoint\": {\"left\": 0.96,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}"; + private const string cropperJson2 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}"; + private const string cropperJson3 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": []}"; private const string mediaPath = "/media/1005/img_0671.jpg"; + + public void CanConvertImageCropperDataSetSrcToString() + { + //cropperJson3 - has not crops + var sourceObj = cropperJson3.SerializeToCropDataSet(); + var destObj = sourceObj.TryConvertTo(); + Assert.IsTrue(destObj.Success); + Assert.AreEqual(destObj.Result, "/media/1005/img_0672.jpg"); + } + + public void CanConvertImageCropperDataSetJObject() + { + //cropperJson3 - has not crops + var sourceObj = cropperJson3.SerializeToCropDataSet(); + var destObj = sourceObj.TryConvertTo(); + Assert.IsTrue(destObj.Success); + Assert.AreEqual(sourceObj, destObj.Result.ToObject()); + } + + public void CanConvertImageCropperDataSetJsonToString() + { + var sourceObj = cropperJson1.SerializeToCropDataSet(); + var destObj = sourceObj.TryConvertTo(); + Assert.IsTrue(destObj.Success); + Assert.IsTrue(destObj.Result.DetectIsJson()); + var obj = JsonConvert.DeserializeObject(cropperJson1); + Assert.AreEqual(sourceObj, obj); + } + + [TestCase(cropperJson1, cropperJson1, true)] + [TestCase(cropperJson1, cropperJson2, false)] + public void CanConvertImageCropperPropertyEditor(string val1, string val2, bool expected) + { + try + { + PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver(new ActivatorServiceProvider(), Mock.Of()); + Resolution.Freeze(); + + var dataTypeService = new Mock(); + dataTypeService.Setup(x => x.GetPreValuesCollectionByDataTypeId(It.IsAny())).Returns(new PreValueCollection(Enumerable.Empty())); + + var converter = new Web.PropertyEditors.ValueConverters.ImageCropperValueConverter(dataTypeService.Object); + var result = converter.ConvertDataToSource(new PublishedPropertyType("test", 0, "test"), val1, false); // does not use type for conversion + + var resultShouldMatch = val2.SerializeToCropDataSet(); + if (expected) + { + Assert.AreEqual(resultShouldMatch, result); + } + else + { + Assert.AreNotEqual(resultShouldMatch, result); + } + } + finally + { + PropertyValueConvertersResolver.Reset(true); + } + } [Test] public void GetCropUrl_CropAliasTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "Thumb", useCropDimensions: true); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, cropAlias: "Thumb", useCropDimensions: true); Assert.AreEqual(mediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } @@ -27,28 +97,28 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_CropAliasIgnoreWidthHeightTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "Thumb", useCropDimensions: true, width: 50, height: 50); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, cropAlias: "Thumb", useCropDimensions: true, width: 50, height: 50); Assert.AreEqual(mediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } [Test] public void GetCropUrl_WidthHeightTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 200, height: 300); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 200, height: 300); Assert.AreEqual(mediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300", urlString); } [Test] public void GetCropUrl_FocalPointTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "thumb", preferFocalPoint: true, useCropDimensions: true); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, cropAlias: "thumb", preferFocalPoint: true, useCropDimensions: true); Assert.AreEqual(mediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=100&height=100", urlString); } [Test] public void GetCropUrlFurtherOptionsTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 200, height: 300, furtherOptions: "&filter=comic&roundedcorners=radius-26|bgcolor-fff"); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 200, height: 300, furtherOptions: "&filter=comic&roundedcorners=radius-26|bgcolor-fff"); Assert.AreEqual(mediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); } @@ -58,7 +128,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrlNullTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "Banner", useCropDimensions: true); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, cropAlias: "Banner", useCropDimensions: true); Assert.AreEqual(null, urlString); } @@ -68,7 +138,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetBaseCropUrlFromModelTest() { - var cropDataSet = cropperJson.SerializeToCropDataSet(); + var cropDataSet = cropperJson1.SerializeToCropDataSet(); var urlString = cropDataSet.GetCropUrl("thumb"); Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } @@ -79,7 +149,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_CropAliasHeightRatioModeTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "Thumb", useCropDimensions: true, ratioMode:ImageCropRatioMode.Height); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode:ImageCropRatioMode.Height); Assert.AreEqual(mediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&heightratio=1", urlString); } @@ -89,7 +159,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_WidthHeightRatioModeTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 300, height: 150, ratioMode:ImageCropRatioMode.Height); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 300, height: 150, ratioMode:ImageCropRatioMode.Height); Assert.AreEqual(mediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=300&heightratio=0.5", urlString); } @@ -99,7 +169,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_HeightWidthRatioModeTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); Assert.AreEqual(mediaPath + "?center=0.80827067669172936,0.96&mode=crop&height=150&widthratio=2", urlString); } @@ -109,7 +179,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_SpecifiedCropModeTest() { - var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 300, height: 150, imageCropMode:ImageCropMode.Max); + var urlString = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 300, height: 150, imageCropMode:ImageCropMode.Max); Assert.AreEqual(mediaPath + "?mode=max&width=300&height=150", urlString); } diff --git a/src/Umbraco.Web/ImageCropperBaseExtensions.cs b/src/Umbraco.Web/ImageCropperBaseExtensions.cs index b870335c91..e993559f1e 100644 --- a/src/Umbraco.Web/ImageCropperBaseExtensions.cs +++ b/src/Umbraco.Web/ImageCropperBaseExtensions.cs @@ -1,38 +1,18 @@ -namespace Umbraco.Web +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Web.Models; + +namespace Umbraco.Web { - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Text; - - using Newtonsoft.Json; - - using Umbraco.Core; - using Umbraco.Core.Logging; - using Umbraco.Web.Models; - internal static class ImageCropperBaseExtensions { - - internal static ImageCropData GetImageCrop(this string json, string id) - { - var ic = new ImageCropData(); - if (json.DetectIsJson()) - { - try - { - var imageCropperSettings = JsonConvert.DeserializeObject>(json); - ic = imageCropperSettings.GetCrop(id); - } - catch (Exception ex) - { - LogHelper.Error(typeof(ImageCropperBaseExtensions), "Could not parse the json string: " + json, ex); - } - } - return ic; - } - internal static ImageCropDataSet SerializeToCropDataSet(this string json) { var imageCrops = new ImageCropDataSet(); @@ -40,7 +20,11 @@ { try { - imageCrops = JsonConvert.DeserializeObject(json); + imageCrops = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + Culture = CultureInfo.InvariantCulture, + FloatParseHandling = FloatParseHandling.Decimal + }); } catch (Exception ex) { @@ -61,13 +45,14 @@ internal static ImageCropData GetCrop(this IEnumerable dataset, string cropAlias) { - if (dataset == null || !dataset.Any()) + var imageCropDatas = dataset.ToArray(); + if (dataset == null || imageCropDatas.Any() == false) return null; if (string.IsNullOrEmpty(cropAlias)) - return dataset.FirstOrDefault(); + return imageCropDatas.FirstOrDefault(); - return dataset.FirstOrDefault(x => x.Alias.ToLowerInvariant() == cropAlias.ToLowerInvariant()); + return imageCropDatas.FirstOrDefault(x => x.Alias.ToLowerInvariant() == cropAlias.ToLowerInvariant()); } internal static string GetCropBaseUrl(this ImageCropDataSet cropDataSet, string cropAlias, bool preferFocalPoint) diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index e592c157aa..bcdea6eb03 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -1,12 +1,13 @@ -namespace Umbraco.Web +using System; +using Newtonsoft.Json.Linq; +using System.Globalization; +using System.Text; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models; + +namespace Umbraco.Web { - using System.Globalization; - using System.Text; - - using Umbraco.Core; - using Umbraco.Core.Models; - using Umbraco.Web.Models; - /// /// Provides extension methods for getting ImageProcessor Url from the core Image Cropper property editor /// @@ -113,35 +114,43 @@ ImageCropRatioMode? ratioMode = null, bool upScale = true) { - string imageCropperValue = null; - - string mediaItemUrl; - - if (mediaItem.HasProperty(propertyAlias) && mediaItem.HasValue(propertyAlias)) - { - //TODO: We should change this, the default value is JObject now, this means we are ToString() ing the value, - // then re-deserializing it back to json and then to a strongly typed model. - // With a tiny bit of work we can make this more efficient since it's already a JObject. - - imageCropperValue = mediaItem.GetPropertyValue(propertyAlias); - - // get the raw value (this will be json) - var urlValue = mediaItem.GetPropertyValue(propertyAlias); - - mediaItemUrl = urlValue.DetectIsJson() - ? urlValue.SerializeToCropDataSet().Src - : urlValue; - } - else - { - mediaItemUrl = mediaItem.Url; - } + if (mediaItem == null) throw new ArgumentNullException("mediaItem"); var cacheBusterValue = cacheBuster ? mediaItem.UpdateDate.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture) : null; - return mediaItemUrl != null - ? GetCropUrl(mediaItemUrl, width, height, imageCropperValue, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale) - : string.Empty; + if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false) + return string.Empty; + + //get the default obj from the value converter + var cropperValue = mediaItem.GetPropertyValue(propertyAlias); + + //is it strongly typed? + var stronglyTyped = cropperValue as ImageCropDataSet; + string mediaItemUrl; + if (stronglyTyped != null) + { + mediaItemUrl = stronglyTyped.Src; + return GetCropUrl( + mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, + cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + //this shouldn't be the case but we'll check + var jobj = cropperValue as JObject; + if (jobj != null) + { + stronglyTyped = jobj.ToObject(); + mediaItemUrl = stronglyTyped.Src; + return GetCropUrl( + mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, + cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + //it's a single string + mediaItemUrl = cropperValue.ToString(); + return GetCropUrl( + mediaItemUrl, width, height, mediaItemUrl, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, + cacheBusterValue, furtherOptions, ratioMode, upScale); } /// @@ -207,49 +216,73 @@ string furtherOptions = null, ImageCropRatioMode? ratioMode = null, bool upScale = true) + { + if (string.IsNullOrEmpty(imageUrl)) return string.Empty; + + ImageCropDataSet cropDataSet = null; + if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) + { + cropDataSet = imageCropperValue.SerializeToCropDataSet(); + } + return GetCropUrl( + imageUrl, cropDataSet, width, height, cropAlias, quality, imageCropMode, + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + public static string GetCropUrl( + this string imageUrl, + ImageCropDataSet cropDataSet, + int? width = null, + int? height = null, + string cropAlias = null, + int? quality = null, + ImageCropMode? imageCropMode = null, + ImageCropAnchor? imageCropAnchor = null, + bool preferFocalPoint = false, + bool useCropDimensions = false, + string cacheBusterValue = null, + string furtherOptions = null, + ImageCropRatioMode? ratioMode = null, + bool upScale = true) { if (string.IsNullOrEmpty(imageUrl) == false) { var imageProcessorUrl = new StringBuilder(); - if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) + if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) { - var cropDataSet = imageCropperValue.SerializeToCropDataSet(); - if (cropDataSet != null) + var crop = cropDataSet.GetCrop(cropAlias); + + imageProcessorUrl.Append(cropDataSet.Src); + + var cropBaseUrl = cropDataSet.GetCropBaseUrl(cropAlias, preferFocalPoint); + if (cropBaseUrl != null) { - var crop = cropDataSet.GetCrop(cropAlias); + imageProcessorUrl.Append(cropBaseUrl); + } + else + { + return null; + } - imageProcessorUrl.Append(cropDataSet.Src); + if (crop != null & useCropDimensions) + { + width = crop.Width; + height = crop.Height; + } - var cropBaseUrl = cropDataSet.GetCropBaseUrl(cropAlias, preferFocalPoint); - if (cropBaseUrl != null) - { - imageProcessorUrl.Append(cropBaseUrl); - } - else - { - return null; - } + // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a width parameter has been passed we can get the crop ratio for the height + if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width != null && height == null) + { + var heightRatio = (decimal)crop.Height / (decimal)crop.Width; + imageProcessorUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); + } - if (crop != null & useCropDimensions) - { - width = crop.Width; - height = crop.Height; - } - - // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a width parameter has been passed we can get the crop ratio for the height - if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width != null && height == null) - { - var heightRatio = (decimal)crop.Height / (decimal)crop.Width; - imageProcessorUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); - } - - // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a height parameter has been passed we can get the crop ratio for the width - if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width == null && height != null) - { - var widthRatio = (decimal)crop.Width / (decimal)crop.Height; - imageProcessorUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); - } + // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a height parameter has been passed we can get the crop ratio for the width + if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width == null && height != null) + { + var widthRatio = (decimal)crop.Width / (decimal)crop.Height; + imageProcessorUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); } } else @@ -310,7 +343,7 @@ if (upScale == false) { - imageProcessorUrl.Append("&upscale=false"); + imageProcessorUrl.Append("&upscale=false"); } if (furtherOptions != null) diff --git a/src/Umbraco.Web/Models/ImageCropCoordinates.cs b/src/Umbraco.Web/Models/ImageCropCoordinates.cs index 5152ba5b42..69a2daee95 100644 --- a/src/Umbraco.Web/Models/ImageCropCoordinates.cs +++ b/src/Umbraco.Web/Models/ImageCropCoordinates.cs @@ -1,9 +1,10 @@ +using System; using System.Runtime.Serialization; namespace Umbraco.Web.Models { [DataContract(Name = "imageCropCoordinates")] - public class ImageCropCoordinates + public class ImageCropCoordinates : IEquatable { [DataMember(Name = "x1")] public decimal X1 { get; set; } @@ -16,5 +17,62 @@ namespace Umbraco.Web.Models [DataMember(Name = "y2")] public decimal Y2 { get; set; } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(ImageCropCoordinates other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return X1 == other.X1 && Y1 == other.Y1 && X2 == other.X2 && Y2 == other.Y2; + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ImageCropCoordinates) obj); + } + + /// + /// Serves as the default hash function. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = X1.GetHashCode(); + hashCode = (hashCode*397) ^ Y1.GetHashCode(); + hashCode = (hashCode*397) ^ X2.GetHashCode(); + hashCode = (hashCode*397) ^ Y2.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ImageCropCoordinates left, ImageCropCoordinates right) + { + return Equals(left, right); + } + + public static bool operator !=(ImageCropCoordinates left, ImageCropCoordinates right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ImageCropData.cs b/src/Umbraco.Web/Models/ImageCropData.cs index f96e209906..5992910c5b 100644 --- a/src/Umbraco.Web/Models/ImageCropData.cs +++ b/src/Umbraco.Web/Models/ImageCropData.cs @@ -1,17 +1,15 @@ -using System.Runtime.Serialization; +using System; +using System.Runtime.Serialization; using System.Threading.Tasks; namespace Umbraco.Web.Models { [DataContract(Name = "imageCropData")] - public class ImageCropData + public class ImageCropData : IEquatable { [DataMember(Name = "alias")] public string Alias { get; set; } - - //[DataMember(Name = "name")] - //public string Name { get; set; } - + [DataMember(Name = "width")] public int Width { get; set; } @@ -20,6 +18,63 @@ namespace Umbraco.Web.Models [DataMember(Name = "coordinates")] public ImageCropCoordinates Coordinates { get; set; } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(ImageCropData other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Alias, other.Alias) && Width == other.Width && Height == other.Height && Equals(Coordinates, other.Coordinates); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ImageCropData) obj); + } + + /// + /// Serves as the default hash function. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = (Alias != null ? Alias.GetHashCode() : 0); + hashCode = (hashCode*397) ^ Width; + hashCode = (hashCode*397) ^ Height; + hashCode = (hashCode*397) ^ (Coordinates != null ? Coordinates.GetHashCode() : 0); + return hashCode; + } + } + + public static bool operator ==(ImageCropData left, ImageCropData right) + { + return Equals(left, right); + } + + public static bool operator !=(ImageCropData left, ImageCropData right) + { + return !Equals(left, right); + } } } diff --git a/src/Umbraco.Web/Models/ImageCropDataSet.cs b/src/Umbraco.Web/Models/ImageCropDataSet.cs index 22ff136c6b..174d52d06f 100644 --- a/src/Umbraco.Web/Models/ImageCropDataSet.cs +++ b/src/Umbraco.Web/Models/ImageCropDataSet.cs @@ -1,14 +1,20 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Web; +using Newtonsoft.Json; +using Umbraco.Core.Serialization; +using Umbraco.Web.PropertyEditors.ValueConverters; namespace Umbraco.Web.Models { + [JsonConverter(typeof(NoTypeConverterJsonConverter))] + [TypeConverter(typeof(ImageCropDataSetConverter))] [DataContract(Name="imageCropDataSet")] - public class ImageCropDataSet : IHtmlString + public class ImageCropDataSet : IHtmlString, IEquatable { [DataMember(Name="src")] public string Src { get; set;} @@ -19,7 +25,6 @@ namespace Umbraco.Web.Models [DataMember(Name = "crops")] public IEnumerable Crops { get; set; } - public string GetCropUrl(string alias, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) { @@ -72,5 +77,73 @@ namespace Umbraco.Web.Models { return this.Src; } + + /// + /// Returns a string that represents the current object. + /// + /// + /// If there are crops defined, it will return the JSON value, otherwise it will just return the Src value + /// + public override string ToString() + { + return Crops.Any() ? JsonConvert.SerializeObject(this) : Src; + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(ImageCropDataSet other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Src, other.Src) && Equals(FocalPoint, other.FocalPoint) + && Crops.SequenceEqual(other.Crops); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ImageCropDataSet) obj); + } + + /// + /// Serves as the default hash function. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = (Src != null ? Src.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (FocalPoint != null ? FocalPoint.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Crops != null ? Crops.GetHashCode() : 0); + return hashCode; + } + } + + public static bool operator ==(ImageCropDataSet left, ImageCropDataSet right) + { + return Equals(left, right); + } + + public static bool operator !=(ImageCropDataSet left, ImageCropDataSet right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ImageCropFocalPoint.cs b/src/Umbraco.Web/Models/ImageCropFocalPoint.cs index e6f12ea81f..d9a0984167 100644 --- a/src/Umbraco.Web/Models/ImageCropFocalPoint.cs +++ b/src/Umbraco.Web/Models/ImageCropFocalPoint.cs @@ -1,14 +1,68 @@ +using System; using System.Runtime.Serialization; namespace Umbraco.Web.Models { [DataContract(Name = "imageCropFocalPoint")] - public class ImageCropFocalPoint{ - + public class ImageCropFocalPoint : IEquatable + { [DataMember(Name = "left")] public decimal Left { get; set; } [DataMember(Name = "top")] public decimal Top { get; set; } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(ImageCropFocalPoint other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Left == other.Left && Top == other.Top; + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ImageCropFocalPoint) obj); + } + + /// + /// Serves as the default hash function. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + unchecked + { + return (Left.GetHashCode()*397) ^ Top.GetHashCode(); + } + } + + public static bool operator ==(ImageCropFocalPoint left, ImageCropFocalPoint right) + { + return Equals(left, right); + } + + public static bool operator !=(ImageCropFocalPoint left, ImageCropFocalPoint right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs new file mode 100644 index 0000000000..82278676cd --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs @@ -0,0 +1,46 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Web.Models; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + /// + /// Used to do some type conversions from ImageCropDataSet to string and JObject + /// + public class ImageCropDataSetConverter : TypeConverter + { + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + var convertableTypes = new[] + { + typeof(JObject) + }; + + return convertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, destinationType)) + || base.CanConvertFrom(context, destinationType); + } + + public override object ConvertTo( + ITypeDescriptorContext context, + CultureInfo culture, + object value, + Type destinationType) + { + var cropDataSet = value as ImageCropDataSet; + if (cropDataSet == null) + return null; + + //JObject + if (TypeHelper.IsTypeAssignableFrom(destinationType)) + { + return JObject.FromObject(cropDataSet); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs new file mode 100644 index 0000000000..a51655b05e --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -0,0 +1,46 @@ +using System.Globalization; +using System.Xml; +using System.Xml.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Dynamics; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + /// + /// Used to strongly type the value for the image cropper + /// + [DefaultPropertyValueConverter(typeof (Core.PropertyEditors.ValueConverters.ImageCropperValueConverter))] //this shadows the Core.PropertyEditors.ValueConverters.ImageCropperValueConverter + [PropertyValueType(typeof (ImageCropDataSet))] + [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] + public class ImageCropperValueConverter : Core.PropertyEditors.ValueConverters.ImageCropperValueConverter + { + public ImageCropperValueConverter() + { + } + + public ImageCropperValueConverter(IDataTypeService dataTypeService) : base(dataTypeService) + { + } + + public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + { + var baseVal = base.ConvertDataToSource(propertyType, source, preview); + var json = baseVal as JObject; + if (json == null) return baseVal; + + var serializer = new JsonSerializer + { + Culture = CultureInfo.InvariantCulture, + FloatParseHandling = FloatParseHandling.Decimal + }; + + //return the strongly typed model + return json.ToObject(serializer); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs index 2278b18b59..8938325c35 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -1,7 +1,11 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Web.Templates; namespace Umbraco.Web.PropertyEditors.ValueConverters @@ -10,9 +14,15 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Request)] public class TextStringValueConverter : PropertyValueConverterBase { + private readonly static string[] PropertyTypeAliases = + { + Constants.PropertyEditors.TextboxAlias, + Constants.PropertyEditors.TextboxMultipleAlias + }; + public override bool IsConverter(PublishedPropertyType propertyType) { - return Constants.PropertyEditors.TextboxAlias.Equals(propertyType.PropertyEditorAlias); + return PropertyTypeAliases.Contains(propertyType.PropertyEditorAlias); } public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ac3fb00af2..e9099f8b8e 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -346,6 +346,8 @@ + + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 932961e0e0..ffbd50dbf7 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -458,6 +458,7 @@ namespace Umbraco.Web // same for other converters PropertyValueConvertersResolver.Current.RemoveType(); PropertyValueConvertersResolver.Current.RemoveType(); + PropertyValueConvertersResolver.Current.RemoveType(); PublishedCachesResolver.Current = new PublishedCachesResolver(new PublishedCaches( new PublishedCache.XmlPublishedCache.PublishedContentCache(),