From 0829bae5b2be43d7d342e276ae291cf750c040c3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 4 Feb 2016 16:03:43 +0100 Subject: [PATCH] Fixes TextStringValueConverter to include the text box and multi-textbox (since it's like that in the Core one that this overrides), adds new converter for Image cropper including ToString converter & custom type converter to convert from the strongly typed object to JObject, adds test to support this, adds equatable methods for the strongly typed model so it can be easily compared. Ensures that the webboot manager does not include the core value converter for image cropper. --- .../Dynamics/DynamicXmlConverter.cs | 6 +- .../ImageCropperValueConverter.cs | 23 ++- .../NoTypeConverterJsonConverter.cs | 52 ++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../PropertyEditors/ImageCropperTest.cs | 98 ++++++++-- src/Umbraco.Web/ImageCropperBaseExtensions.cs | 57 +++--- .../ImageCropperTemplateExtensions.cs | 169 +++++++++++------- .../Models/ImageCropCoordinates.cs | 60 ++++++- src/Umbraco.Web/Models/ImageCropData.cs | 67 ++++++- src/Umbraco.Web/Models/ImageCropDataSet.cs | 77 +++++++- src/Umbraco.Web/Models/ImageCropFocalPoint.cs | 58 +++++- .../ImageCropDataSetConverter.cs | 46 +++++ .../ImageCropperValueConverter.cs | 46 +++++ .../TextStringValueConverter.cs | 12 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 + src/Umbraco.Web/WebBootManager.cs | 1 + 16 files changed, 640 insertions(+), 135 deletions(-) create mode 100644 src/Umbraco.Core/Serialization/NoTypeConverterJsonConverter.cs create mode 100644 src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs create mode 100644 src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs 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(),