From b182141d6c36782d54702d49c2660900f74140fc Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 6 Feb 2020 17:33:22 +0100 Subject: [PATCH 1/7] Fix jumping section name on focus --- src/Umbraco.Web.UI.Client/src/less/sections.less | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index 40921c5b76..27b11b1c3b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -41,13 +41,13 @@ ul.sections { &:focus .section__name { .tabbing-active & { - border: 1px solid; - border-color: @gray-9; + border: 1px solid @gray-9; } } } .section__name { + border: 1px solid transparent; border-radius: 3px; margin-top: 1px; padding: 3px 10px 4px 10px; @@ -75,9 +75,9 @@ ul.sections { opacity: 0.6; transition: opacity .1s linear; } - + &:hover i { - opacity:1; + opacity: 1; } } From 02a9645ed17cc6b31cafc3cd1edfd745dabb7809 Mon Sep 17 00:00:00 2001 From: Enkel Media Date: Fri, 7 Feb 2020 00:03:53 +0100 Subject: [PATCH 2/7] #6812 AuditService some methods not returning any values (#6822) * #6812 Worked on AuditService some methods not returning any values * Changed userId to include also admin-user * Use Constant for "SuperUserId" over magic int Co-authored-by: markusobviuse <36044239+markusobviuse@users.noreply.github.com> --- .../Persistence/Repositories/Implement/AuditRepository.cs | 3 ++- src/Umbraco.Core/Services/Implement/AuditService.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs index 0788594e3a..4cb533e86f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs @@ -77,7 +77,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public IEnumerable Get(AuditType type, IQuery query) { var sqlClause = GetBaseQuery(false) - .Where(x => x.Header == type.ToString()); + .Where("(logHeader=@0)", type.ToString()); + var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); diff --git a/src/Umbraco.Core/Services/Implement/AuditService.cs b/src/Umbraco.Core/Services/Implement/AuditService.cs index 5eb08f2dea..7d3be1d52b 100644 --- a/src/Umbraco.Core/Services/Implement/AuditService.cs +++ b/src/Umbraco.Core/Services/Implement/AuditService.cs @@ -142,7 +142,7 @@ namespace Umbraco.Core.Services.Implement if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - if (userId < 0) + if (userId < Constants.Security.SuperUserId) { totalRecords = 0; return Enumerable.Empty(); From a7ff20de6e62ae47bae3dde63f9ce3708034a3fc Mon Sep 17 00:00:00 2001 From: Benjamin Carleski Date: Fri, 7 Feb 2020 15:01:03 -0800 Subject: [PATCH 3/7] Introduce Image URL Generator abstraction --- src/Umbraco.Core/Composing/Current.cs | 3 + src/Umbraco.Core/Models/IImageUrlGenerator.cs | 7 + .../Models/ImageUrlGenerationOptions.cs | 35 ++ src/Umbraco.Core/Models/UserExtensions.cs | 11 +- .../ValueConverters/ImageCropperValue.cs | 63 ++- src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../PropertyEditors/ImageCropperTest.cs | 98 +++-- .../PublishedContentTestBase.cs | 3 +- .../PublishedContent/PublishedContentTests.cs | 3 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 1 + src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 2 + .../Views/Partials/Grid/Editors/Media.cshtml | 22 +- src/Umbraco.Web/Editors/ImagesController.cs | 18 +- .../ImageCropperTemplateCoreExtensions.cs | 389 ++++++++++++++++++ .../ImageCropperTemplateExtensions.cs | 186 +-------- .../Models/ImageProcessorImageUrlGenerator.cs | 108 +++++ .../PropertyEditors/GridPropertyEditor.cs | 33 +- .../RichTextEditorPastedImages.cs | 4 +- .../PropertyEditors/RichTextPropertyEditor.cs | 20 +- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 4 + .../Templates/TemplateUtilities.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 + 22 files changed, 747 insertions(+), 269 deletions(-) create mode 100644 src/Umbraco.Core/Models/IImageUrlGenerator.cs create mode 100644 src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs create mode 100644 src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs create mode 100644 src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index 846331a16e..a06f09baf6 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Dictionary; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PackageActions; using Umbraco.Core.Packaging; @@ -208,6 +209,8 @@ namespace Umbraco.Core.Composing public static IVariationContextAccessor VariationContextAccessor => Factory.GetInstance(); + public static IImageUrlGenerator ImageUrlGenerator + => Factory.GetInstance(); #endregion } } diff --git a/src/Umbraco.Core/Models/IImageUrlGenerator.cs b/src/Umbraco.Core/Models/IImageUrlGenerator.cs new file mode 100644 index 0000000000..0dc2933fd9 --- /dev/null +++ b/src/Umbraco.Core/Models/IImageUrlGenerator.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Models +{ + public interface IImageUrlGenerator + { + string GetImageUrl(ImageUrlGenerationOptions options); + } +} diff --git a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs new file mode 100644 index 0000000000..9e8d3f3a00 --- /dev/null +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -0,0 +1,35 @@ +namespace Umbraco.Core.Models +{ + public class ImageUrlGenerationOptions + { + public string ImageUrl { get; set; } + public int? Width { get; set; } + public int? Height { get; set; } + public decimal? WidthRatio { get; set; } + public decimal? HeightRatio { get; set; } + public int? Quality { get; set; } + public string ImageCropMode { get; set; } + public string ImageCropAnchor { get; set; } + public bool DefaultCrop { get; set; } + public FocalPointPosition FocalPoint { get; set; } + public CropCoordinates Crop { get; set; } + public string CacheBusterValue { get; set; } + public string FurtherOptions { get; set; } + public bool UpScale { get; set; } = true; + public string AnimationProcessMode { get; set; } + + public class FocalPointPosition + { + public decimal Left { get; set; } + public decimal Top { get; set; } + } + + public class CropCoordinates + { + public decimal X1 { get; set; } + public decimal Y1 { get; set; } + public decimal X2 { get; set; } + public decimal Y2 { get; set; } + } + } +} diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index e00ac4ba15..f11a8efe46 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -106,13 +106,14 @@ namespace Umbraco.Core.Models //use the custom avatar var avatarUrl = Current.MediaFileSystem.GetUrl(user.Avatar); + var urlGenerator = Current.ImageUrlGenerator; return new[] { - avatarUrl + "?width=30&height=30&mode=crop", - avatarUrl + "?width=60&height=60&mode=crop", - avatarUrl + "?width=90&height=90&mode=crop", - avatarUrl + "?width=150&height=150&mode=crop", - avatarUrl + "?width=300&height=300&mode=crop" + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 30, Height = 30 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 60, Height = 60 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 90, Height = 90 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 150, Height = 150 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 300, Height = 300 }) }; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs index b211272b51..70eb7e0477 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -7,6 +7,8 @@ using System.Runtime.Serialization; using System.Text; using System.Web; using Newtonsoft.Json; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; using Umbraco.Core.Serialization; namespace Umbraco.Core.PropertyEditors.ValueConverters @@ -59,38 +61,34 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters : Crops.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } - internal void AppendCropBaseUrl(StringBuilder url, ImageCropperCrop crop, bool defaultCrop, bool preferFocalPoint) + internal ImageUrlGenerationOptions GetCropBaseOptions(string url, ImageCropperCrop crop, bool defaultCrop, bool preferFocalPoint) { if (preferFocalPoint && HasFocalPoint() || crop != null && crop.Coordinates == null && HasFocalPoint() || defaultCrop && HasFocalPoint()) { - url.Append("?center="); - url.Append(FocalPoint.Top.ToString(CultureInfo.InvariantCulture)); - url.Append(","); - url.Append(FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); - url.Append("&mode=crop"); + return new ImageUrlGenerationOptions { ImageUrl = url, FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition { Left = FocalPoint.Left, Top = FocalPoint.Top } }; } else if (crop != null && crop.Coordinates != null && preferFocalPoint == false) { - url.Append("?crop="); - url.Append(crop.Coordinates.X1.ToString(CultureInfo.InvariantCulture)).Append(","); - url.Append(crop.Coordinates.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); - url.Append(crop.Coordinates.X2.ToString(CultureInfo.InvariantCulture)).Append(","); - url.Append(crop.Coordinates.Y2.ToString(CultureInfo.InvariantCulture)); - url.Append("&cropmode=percentage"); + return new ImageUrlGenerationOptions { ImageUrl = url, Crop = new ImageUrlGenerationOptions.CropCoordinates { X1 = crop.Coordinates.X1, X2 = crop.Coordinates.X2, Y1 = crop.Coordinates.Y1, Y2 = crop.Coordinates.Y2 } }; } else { - url.Append("?anchor=center"); - url.Append("&mode=crop"); + return new ImageUrlGenerationOptions { ImageUrl = url, DefaultCrop = true }; } } /// /// Gets the value image url for a specified crop. /// - public string GetCropUrl(string alias, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) + [Obsolete("Use the overload that takes an IImageUrlGenerator")] + public string GetCropUrl(string alias, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) => GetCropUrl(alias, Current.ImageUrlGenerator, useCropDimensions, useFocalPoint, cacheBusterValue); + + /// + /// Gets the value image url for a specified crop. + /// + public string GetCropUrl(string alias, IImageUrlGenerator imageUrlGenerator, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) { var crop = GetCrop(alias); @@ -98,38 +96,37 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (crop == null && !string.IsNullOrWhiteSpace(alias)) return null; - var url = new StringBuilder(); - - AppendCropBaseUrl(url, crop, string.IsNullOrWhiteSpace(alias), useFocalPoint); + var options = GetCropBaseOptions(string.Empty, crop, string.IsNullOrWhiteSpace(alias), useFocalPoint); if (crop != null && useCropDimensions) { - url.Append("&width=").Append(crop.Width); - url.Append("&height=").Append(crop.Height); + options.Width = crop.Width; + options.Height = crop.Height; } - if (cacheBusterValue != null) - url.Append("&rnd=").Append(cacheBusterValue); + options.CacheBusterValue = cacheBusterValue; - return url.ToString(); + return imageUrlGenerator.GetImageUrl(options); } /// /// Gets the value image url for a specific width and height. /// - public string GetCropUrl(int width, int height, bool useFocalPoint = false, string cacheBusterValue = null) + [Obsolete("Use the overload that takes an IImageUrlGenerator")] + public string GetCropUrl(int width, int height, bool useFocalPoint = false, string cacheBusterValue = null) => GetCropUrl(width, height, Current.ImageUrlGenerator, useFocalPoint, cacheBusterValue); + + /// + /// Gets the value image url for a specific width and height. + /// + public string GetCropUrl(int width, int height, IImageUrlGenerator imageUrlGenerator, bool useFocalPoint = false, string cacheBusterValue = null) { - var url = new StringBuilder(); + var options = GetCropBaseOptions(string.Empty, null, true, useFocalPoint); - AppendCropBaseUrl(url, null, true, useFocalPoint); + options.Width = width; + options.Height = height; + options.CacheBusterValue = cacheBusterValue; - url.Append("&width=").Append(width); - url.Append("&height=").Append(height); - - if (cacheBusterValue != null) - url.Append("&rnd=").Append(cacheBusterValue); - - return url.ToString(); + return imageUrlGenerator.GetImageUrl(options); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 1fc3709e88..3cf7ba7839 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -132,6 +132,8 @@ + + diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 433ba64b38..62c0792f7b 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -21,6 +21,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Web.Models; using Umbraco.Web; using Umbraco.Web.PropertyEditors; +using System.Text; namespace Umbraco.Tests.PropertyEditors { @@ -110,7 +111,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_CropAliasTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true); Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } @@ -120,28 +121,28 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_CropAliasIgnoreWidthHeightTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, width: 50, height: 50); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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: CropperJson1, width: 200, height: 300); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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: CropperJson1, cropAlias: "thumb", preferFocalPoint: true, useCropDimensions: true); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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: CropperJson1, width: 200, height: 300, furtherOptions: "&filter=comic&roundedcorners=radius-26|bgcolor-fff"); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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); } @@ -151,7 +152,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrlNullTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, cropAlias: "Banner", useCropDimensions: true); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Banner", useCropDimensions: true); Assert.AreEqual(null, urlString); } @@ -162,7 +163,7 @@ namespace Umbraco.Tests.PropertyEditors public void GetBaseCropUrlFromModelTest() { var cropDataSet = CropperJson1.DeserializeImageCropperValue(); - var urlString = cropDataSet.GetCropUrl("thumb"); + var urlString = cropDataSet.GetCropUrl("thumb", new TestImageUrlGenerator()); Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } @@ -172,7 +173,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_CropAliasHeightRatioModeTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode:ImageCropRatioMode.Height); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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); } @@ -182,7 +183,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_WidthHeightRatioModeTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode:ImageCropRatioMode.Height); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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); } @@ -192,7 +193,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_HeightWidthRatioModeTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&height=150&widthratio=2", urlString); } @@ -202,11 +203,11 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_SpecifiedCropModeTest() { - var urlStringMin = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Min); - var urlStringBoxPad = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.BoxPad); - var urlStringPad = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Pad); - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode:ImageCropMode.Max); - var urlStringStretch = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Stretch); + var urlStringMin = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Min); + var urlStringBoxPad = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.BoxPad); + var urlStringPad = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Pad); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode:ImageCropMode.Max); + var urlStringStretch = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Stretch); Assert.AreEqual(MediaPath + "?mode=min&width=300&height=150", urlStringMin); Assert.AreEqual(MediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad); @@ -221,7 +222,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_UploadTypeTest() { - var urlString = MediaPath.GetCropUrl(width: 100, height: 270, imageCropMode: ImageCropMode.Crop, imageCropAnchor: ImageCropAnchor.Center); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), width: 100, height: 270, imageCropMode: ImageCropMode.Crop, imageCropAnchor: ImageCropAnchor.Center); Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString); } @@ -233,7 +234,7 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"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}}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 300, height: 150, preferFocalPoint:true); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 300, height: 150, preferFocalPoint:true); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); } @@ -245,7 +246,7 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", width: 200); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); } @@ -257,7 +258,7 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", width: 200); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200); Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); } @@ -269,7 +270,7 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", width: 200, useCropDimensions: true); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200, useCropDimensions: true); Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString); } @@ -281,7 +282,7 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", height: 200); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", height: 200); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); } @@ -293,7 +294,7 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 200); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 200); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=200", urlString); } @@ -305,7 +306,7 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, height: 200); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, height: 200); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&height=200", urlString); } @@ -317,8 +318,57 @@ namespace Umbraco.Tests.PropertyEditors { var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"" + MediaPath + "\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, furtherOptions: "&bgcolor=fff"); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, furtherOptions: "&bgcolor=fff"); Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); } + + private class TestImageUrlGenerator : IImageUrlGenerator + { + public string GetImageUrl(ImageUrlGenerationOptions options) + { + var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty); + + if (options.FocalPoint != null) + { + imageProcessorUrl.Append("?center="); + imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append(","); + imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&mode=crop"); + } + else if (options.Crop != null) + { + imageProcessorUrl.Append("?crop="); + imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&cropmode=percentage"); + } + else if (options.DefaultCrop) + { + imageProcessorUrl.Append("?anchor=center&mode=crop"); + } + else + { + imageProcessorUrl.Append("?mode=" + options.ImageCropMode.ToString().ToLower()); + if (options.ImageCropAnchor != null)imageProcessorUrl.Append("&anchor=" + options.ImageCropAnchor.ToString().ToLower()); + } + + var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); + if (options.Quality != null && hasFormat == false) imageProcessorUrl.Append("&quality=" + options.Quality); + if (options.HeightRatio != null) imageProcessorUrl.Append("&heightratio=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.WidthRatio != null) imageProcessorUrl.Append("&widthratio=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.Width != null) imageProcessorUrl.Append("&width=" + options.Width); + if (options.Height != null) imageProcessorUrl.Append("&height=" + options.Height); + if (options.UpScale == false) imageProcessorUrl.Append("&upscale=false"); + if (options.AnimationProcessMode != null) imageProcessorUrl.Append("&animationprocessmode=" + options.AnimationProcessMode); + if (options.FurtherOptions != null) imageProcessorUrl.Append(options.FurtherOptions); + if (options.Quality != null && hasFormat) imageProcessorUrl.Append("&quality=" + options.Quality); + if (options.CacheBusterValue != null) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue); + + return imageProcessorUrl.ToString(); + } + } } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index a96cad4076..59791fc645 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -12,6 +12,7 @@ using Umbraco.Web.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.Templates; +using Umbraco.Web.Models; namespace Umbraco.Tests.PublishedContent { @@ -46,7 +47,7 @@ namespace Umbraco.Tests.PublishedContent var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); var localLinkParser = new HtmlLocalLinkParser(umbracoContextAccessor); var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages)) { Id = 1 }); + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, Mock.Of())) { Id = 1 }); var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index be63e3b5da..998fc92380 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -22,6 +22,7 @@ using Umbraco.Tests.Testing; using Umbraco.Web.Models.PublishedContent; using Umbraco.Web.PropertyEditors; using Umbraco.Web.Templates; +using Umbraco.Web.Models; namespace Umbraco.Tests.PublishedContent { @@ -53,7 +54,7 @@ namespace Umbraco.Tests.PublishedContent var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new VoidEditor(logger)) { Id = 1 }, new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 }, - new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, linkParser, pastedImages)) { Id = 1002 }, + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, linkParser, pastedImages, Mock.Of())) { Id = 1002 }, new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 }, new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 }, new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 }); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 7ab329b9a0..9157a76773 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -97,6 +97,7 @@ namespace Umbraco.Tests.Runtimes composition.Register(Lifetime.Singleton); composition.Register(Lifetime.Singleton); composition.Register(_ => Mock.Of(), Lifetime.Singleton); + composition.Register(_ => Mock.Of(), Lifetime.Singleton); composition.RegisterUnique(f => new DistributedCache()); composition.WithCollectionBuilder().Append(); composition.RegisterUnique(); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 4f7de2d230..468d4e8b03 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -43,6 +43,7 @@ using Current = Umbraco.Core.Composing.Current; using FileSystems = Umbraco.Core.IO.FileSystems; using Umbraco.Web.Templates; using Umbraco.Web.PropertyEditors; +using Umbraco.Core.Models; namespace Umbraco.Tests.Testing { @@ -248,6 +249,7 @@ namespace Umbraco.Tests.Testing var runtimeStateMock = new Mock(); runtimeStateMock.Setup(x => x.Level).Returns(RuntimeLevel.Run); Composition.RegisterUnique(f => runtimeStateMock.Object); + Composition.Register(_ => Mock.Of()); // ah... Composition.WithCollectionBuilder(); diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml index ea79ce41ad..e20b717ed5 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml @@ -2,20 +2,24 @@ @using Umbraco.Web.Templates @if (Model.value != null) -{ +{ var url = Model.value.image; if(Model.editor.config != null && Model.editor.config.size != null){ - url += "?width=" + Model.editor.config.size.width; - url += "&height=" + Model.editor.config.size.height; - - if(Model.value.focalPoint != null){ - url += "¢er=" + Model.value.focalPoint.top +"," + Model.value.focalPoint.left; - url += "&mode=crop"; - } + url = ImageCropperTemplateExtensions.GetCropUrl(url, + width: Model.editor.config.size.width, + height: Model.editor.config.size.height, + cropDataSet: Model.value.focalPoint == null ? null : new Umbraco.Core.PropertyEditors.ValueConverters.ImageCropperValue + { + FocalPoint = new Umbraco.Core.PropertyEditors.ValueConverters.ImageCropperValue.ImageCropperFocalPoint + { + Top = Model.value.focalPoint.top, + Left = Model.value.focalPoint.left + } + }); } var altText = Model.value.altText ?? Model.value.caption ?? string.Empty; - + @altText if (Model.value.caption != null) diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index db6706d1bb..c8f0e2d9bf 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -2,8 +2,10 @@ using System.IO; using System.Net; using System.Net.Http; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; +using Umbraco.Core.Models; using Umbraco.Web.Media; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -19,11 +21,17 @@ namespace Umbraco.Web.Editors { private readonly IMediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSection; + private readonly IImageUrlGenerator _imageUrlGenerator; - public ImagesController(IMediaFileSystem mediaFileSystem, IContentSection contentSection) + [Obsolete("This constructor will be removed in a future release. Please use the constructor with the IImageUrlGenerator overload")] + public ImagesController(IMediaFileSystem mediaFileSystem, IContentSection contentSection) : this (mediaFileSystem, contentSection, Current.ImageUrlGenerator) + { + } + public ImagesController(IMediaFileSystem mediaFileSystem, IContentSection contentSection, IImageUrlGenerator imageUrlGenerator) { _mediaFileSystem = mediaFileSystem; _contentSection = contentSection; + _imageUrlGenerator = imageUrlGenerator; } /// @@ -75,12 +83,10 @@ namespace Umbraco.Web.Editors // so ignore and we won't set a last modified date. } - // TODO: When we abstract imaging for netcore, we are actually just going to be abstracting a URI builder for images, this - // is one of those places where this can be used. + var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null; + var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = imagePath, UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = "max", CacheBusterValue = rnd }); - var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : string.Empty; - - response.Headers.Location = new Uri($"{imagePath}?upscale=false&width={width}&animationprocessmode=first&mode=max{rnd}", UriKind.RelativeOrAbsolute); + response.Headers.Location = new Uri(imageUrl, UriKind.RelativeOrAbsolute); return response; } diff --git a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs new file mode 100644 index 0000000000..b8e832beb9 --- /dev/null +++ b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs @@ -0,0 +1,389 @@ +using System; +using Newtonsoft.Json.Linq; +using System.Globalization; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Web.Models; + +namespace Umbraco.Web +{ + public static class ImageCropperTemplateCoreExtensions + { + /// + /// Gets the ImageProcessor Url by the crop alias (from the "umbracoFile" property alias) on the IPublishedContent item + /// + /// + /// The IPublishedContent item. + /// + /// + /// The crop alias e.g. thumbnail + /// + /// + /// The ImageProcessor.Web Url. + /// + public static string GetCropUrl(this IPublishedContent mediaItem, string cropAlias, IImageUrlGenerator imageUrlGenerator) + { + return mediaItem.GetCropUrl(imageUrlGenerator, cropAlias: cropAlias, useCropDimensions: true); + } + + /// + /// Gets the ImageProcessor Url by the crop alias using the specified property containing the image cropper Json data on the IPublishedContent item. + /// + /// + /// The IPublishedContent item. + /// + /// + /// The property alias of the property containing the Json data e.g. umbracoFile + /// + /// + /// The crop alias e.g. thumbnail + /// + /// + /// The ImageProcessor.Web Url. + /// + public static string GetCropUrl(this IPublishedContent mediaItem, string propertyAlias, string cropAlias, IImageUrlGenerator imageUrlGenerator) + { + return mediaItem.GetCropUrl(imageUrlGenerator, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); + } + + /// + /// Gets the ImageProcessor Url from the IPublishedContent item. + /// + /// + /// The IPublishedContent item. + /// + /// + /// The width of the output image. + /// + /// + /// The height of the output image. + /// + /// + /// Property alias of the property containing the Json data. + /// + /// + /// The crop alias. + /// + /// + /// Quality percentage of the output image. + /// + /// + /// The image crop mode. + /// + /// + /// The image crop anchor. + /// + /// + /// Use focal point, to generate an output image using the focal point instead of the predefined crop + /// + /// + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. + /// + /// + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated + /// + /// + /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: + /// + /// + /// + /// + /// + /// Use a dimension as a ratio + /// + /// + /// If the image should be upscaled to requested dimensions + /// + /// + /// The . + /// + public static string GetCropUrl( + this IPublishedContent mediaItem, + IImageUrlGenerator imageUrlGenerator, + int? width = null, + int? height = null, + string propertyAlias = Constants.Conventions.Media.File, + string cropAlias = null, + int? quality = null, + ImageCropMode? imageCropMode = null, + ImageCropAnchor? imageCropAnchor = null, + bool preferFocalPoint = false, + bool useCropDimensions = false, + bool cacheBuster = true, + string furtherOptions = null, + ImageCropRatioMode? ratioMode = null, + bool upScale = true) + { + if (mediaItem == null) throw new ArgumentNullException("mediaItem"); + + var cacheBusterValue = cacheBuster ? mediaItem.UpdateDate.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture) : null; + + if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false) + return string.Empty; + + var mediaItemUrl = mediaItem.MediaUrl(propertyAlias: propertyAlias); + + //get the default obj from the value converter + var cropperValue = mediaItem.Value(propertyAlias); + + //is it strongly typed? + var stronglyTyped = cropperValue as ImageCropperValue; + if (stronglyTyped != null) + { + return GetCropUrl( + mediaItemUrl, imageUrlGenerator, 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(); + return GetCropUrl( + mediaItemUrl, imageUrlGenerator, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, + cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + //it's a single string + return GetCropUrl( + mediaItemUrl, imageUrlGenerator, width, height, mediaItemUrl, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, + cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + /// + /// Gets the ImageProcessor Url from the image path. + /// + /// + /// The image url. + /// + /// + /// The width of the output image. + /// + /// + /// The height of the output image. + /// + /// + /// The Json data from the Umbraco Core Image Cropper property editor + /// + /// + /// The crop alias. + /// + /// + /// Quality percentage of the output image. + /// + /// + /// The image crop mode. + /// + /// + /// The image crop anchor. + /// + /// + /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one + /// + /// + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters + /// + /// + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated + /// + /// + /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: + /// + /// + /// + /// + /// + /// Use a dimension as a ratio + /// + /// + /// If the image should be upscaled to requested dimensions + /// + /// + /// The . + /// + public static string GetCropUrl( + this string imageUrl, + IImageUrlGenerator imageUrlGenerator, + int? width = null, + int? height = null, + string imageCropperValue = 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)) return string.Empty; + + ImageCropperValue cropDataSet = null; + if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) + { + cropDataSet = imageCropperValue.DeserializeImageCropperValue(); + } + return GetCropUrl( + imageUrl, imageUrlGenerator, cropDataSet, width, height, cropAlias, quality, imageCropMode, + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + /// + /// Gets the ImageProcessor Url from the image path. + /// + /// + /// The image url. + /// + /// + /// The generator that will process all the options and the image URL to return a full image urls with all processing options appended + /// + /// + /// + /// The width of the output image. + /// + /// + /// The height of the output image. + /// + /// + /// The crop alias. + /// + /// + /// Quality percentage of the output image. + /// + /// + /// The image crop mode. + /// + /// + /// The image crop anchor. + /// + /// + /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one + /// + /// + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters + /// + /// + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated + /// + /// + /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: + /// + /// + /// + /// + /// + /// Use a dimension as a ratio + /// + /// + /// If the image should be upscaled to requested dimensions + /// + /// + /// The . + /// + public static string GetCropUrl( + this string imageUrl, + IImageUrlGenerator imageUrlGenerator, + ImageCropperValue 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) + { + ImageUrlGenerationOptions options; + + if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) + { + var crop = cropDataSet.GetCrop(cropAlias); + + // if a crop was specified, but not found, return null + if (crop == null && !string.IsNullOrWhiteSpace(cropAlias)) + return null; + + options = cropDataSet.GetCropBaseOptions(imageUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint); + + 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) + { + options.HeightRatio = (decimal)crop.Height / crop.Width; + } + + // 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) + { + options.WidthRatio = (decimal)crop.Width / crop.Height; + } + } + else + { + options = new ImageUrlGenerationOptions + { + ImageUrl = imageUrl, + ImageCropMode = (imageCropMode ?? ImageCropMode.Pad).ToString().ToLowerInvariant(), + ImageCropAnchor = imageCropAnchor?.ToString().ToLowerInvariant() + }; + } + + options.Quality = quality; + options.Width = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Width ? null : width; + options.Height = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Height ? null : height; + + if (ratioMode == ImageCropRatioMode.Width && height != null) + { + // if only height specified then assume a square + if (width == null) + { + width = height; + } + + options.WidthRatio = (decimal)width / (decimal)height; + } + + if (ratioMode == ImageCropRatioMode.Height && width != null) + { + // if only width specified then assume a square + if (height == null) + { + height = width; + } + + options.HeightRatio = (decimal)height / (decimal)width; + } + + options.UpScale = upScale; + options.FurtherOptions = furtherOptions; + options.CacheBusterValue = cacheBusterValue; + + return imageUrlGenerator.GetImageUrl(options); + } + + return string.Empty; + } + } +} diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index 656f1e05a2..f3833ba42a 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -29,10 +29,7 @@ namespace Umbraco.Web /// /// The ImageProcessor.Web Url. /// - public static string GetCropUrl(this IPublishedContent mediaItem, string cropAlias) - { - return mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true); - } + public static string GetCropUrl(this IPublishedContent mediaItem, string cropAlias) => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, cropAlias, Current.ImageUrlGenerator); /// /// Gets the ImageProcessor Url by the crop alias using the specified property containing the image cropper Json data on the IPublishedContent item. @@ -49,10 +46,7 @@ namespace Umbraco.Web /// /// The ImageProcessor.Web Url. /// - public static string GetCropUrl(this IPublishedContent mediaItem, string propertyAlias, string cropAlias) - { - return mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); - } + public static string GetCropUrl(this IPublishedContent mediaItem, string propertyAlias, string cropAlias) => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, propertyAlias, cropAlias, Current.ImageUrlGenerator); /// /// Gets the ImageProcessor Url from the IPublishedContent item. @@ -121,44 +115,7 @@ namespace Umbraco.Web bool cacheBuster = true, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - if (mediaItem == null) throw new ArgumentNullException("mediaItem"); - - var cacheBusterValue = cacheBuster ? mediaItem.UpdateDate.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture) : null; - - if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false) - return string.Empty; - - var mediaItemUrl = mediaItem.MediaUrl(propertyAlias: propertyAlias); - - //get the default obj from the value converter - var cropperValue = mediaItem.Value(propertyAlias); - - //is it strongly typed? - var stronglyTyped = cropperValue as ImageCropperValue; - if (stronglyTyped != null) - { - 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(); - return GetCropUrl( - mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode, upScale); - } - - //it's a single string - return GetCropUrl( - mediaItemUrl, width, height, mediaItemUrl, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode, upScale); - } + bool upScale = true) => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, Current.ImageUrlGenerator, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, upScale); /// /// Gets the ImageProcessor Url from the image path. @@ -227,19 +184,7 @@ namespace Umbraco.Web string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - if (string.IsNullOrEmpty(imageUrl)) return string.Empty; - - ImageCropperValue cropDataSet = null; - if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) - { - cropDataSet = imageCropperValue.DeserializeImageCropperValue(); - } - return GetCropUrl( - imageUrl, cropDataSet, width, height, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); - } + bool upScale = true) => ImageCropperTemplateCoreExtensions.GetCropUrl(imageUrl, Current.ImageUrlGenerator, width, height, imageCropperValue, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); /// /// Gets the ImageProcessor Url from the image path. @@ -306,129 +251,8 @@ namespace Umbraco.Web string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - if (string.IsNullOrEmpty(imageUrl) == false) - { - var imageProcessorUrl = new StringBuilder(); + bool upScale = true) => ImageCropperTemplateCoreExtensions.GetCropUrl(imageUrl, Current.ImageUrlGenerator, cropDataSet, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); - if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) - { - var crop = cropDataSet.GetCrop(cropAlias); - - // if a crop was specified, but not found, return null - if (crop == null && !string.IsNullOrWhiteSpace(cropAlias)) - return null; - - imageProcessorUrl.Append(imageUrl); - cropDataSet.AppendCropBaseUrl(imageProcessorUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint); - - 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)); - } - } - else - { - imageProcessorUrl.Append(imageUrl); - - if (imageCropMode == null) - { - imageCropMode = ImageCropMode.Pad; - } - - imageProcessorUrl.Append("?mode=" + imageCropMode.ToString().ToLower()); - - if (imageCropAnchor != null) - { - imageProcessorUrl.Append("&anchor=" + imageCropAnchor.ToString().ToLower()); - } - } - - var hasFormat = furtherOptions != null && furtherOptions.InvariantContains("&format="); - - //Only put quality here, if we don't have a format specified. - //Otherwise we need to put quality at the end to avoid it being overridden by the format. - if (quality != null && hasFormat == false) - { - imageProcessorUrl.Append("&quality=" + quality); - } - - if (width != null && ratioMode != ImageCropRatioMode.Width) - { - imageProcessorUrl.Append("&width=" + width); - } - - if (height != null && ratioMode != ImageCropRatioMode.Height) - { - imageProcessorUrl.Append("&height=" + height); - } - - if (ratioMode == ImageCropRatioMode.Width && height != null) - { - // if only height specified then assume a square - if (width == null) - { - width = height; - } - - var widthRatio = (decimal)width / (decimal)height; - imageProcessorUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); - } - - if (ratioMode == ImageCropRatioMode.Height && width != null) - { - // if only width specified then assume a square - if (height == null) - { - height = width; - } - - var heightRatio = (decimal)height / (decimal)width; - imageProcessorUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); - } - - if (upScale == false) - { - imageProcessorUrl.Append("&upscale=false"); - } - - if (furtherOptions != null) - { - imageProcessorUrl.Append(furtherOptions); - } - - //If furtherOptions contains a format, we need to put the quality after the format. - if (quality != null && hasFormat) - { - imageProcessorUrl.Append("&quality=" + quality); - } - - if (cacheBusterValue != null) - { - imageProcessorUrl.Append("&rnd=").Append(cacheBusterValue); - } - - return imageProcessorUrl.ToString(); - } - - return string.Empty; - } internal static ImageCropperValue DeserializeImageCropperValue(this string json) { diff --git a/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs b/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs new file mode 100644 index 0000000000..816cfda0e9 --- /dev/null +++ b/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models +{ + internal class ImageProcessorImageUrlGenerator : IImageUrlGenerator + { + public string GetImageUrl(ImageUrlGenerationOptions options) + { + var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty); + + if (options.FocalPoint != null) + { + imageProcessorUrl.Append("?center="); + imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append(","); + imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&mode=crop"); + } + else if (options.Crop != null) + { + imageProcessorUrl.Append("?crop="); + imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&cropmode=percentage"); + } + else if (options.DefaultCrop) + { + imageProcessorUrl.Append("?anchor=center"); + imageProcessorUrl.Append("&mode=crop"); + } + else + { + imageProcessorUrl.Append("?mode=" + options.ImageCropMode.ToString().ToLower()); + + if (options.ImageCropAnchor != null) + { + imageProcessorUrl.Append("&anchor=" + options.ImageCropAnchor.ToString().ToLower()); + } + } + + var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); + + //Only put quality here, if we don't have a format specified. + //Otherwise we need to put quality at the end to avoid it being overridden by the format. + if (options.Quality != null && hasFormat == false) + { + imageProcessorUrl.Append("&quality=" + options.Quality); + } + + if (options.HeightRatio != null) + { + imageProcessorUrl.Append("&heightratio=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (options.WidthRatio != null) + { + imageProcessorUrl.Append("&widthratio=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (options.Width != null) + { + imageProcessorUrl.Append("&width=" + options.Width); + } + + if (options.Height != null) + { + imageProcessorUrl.Append("&height=" + options.Height); + } + + if (options.UpScale == false) + { + imageProcessorUrl.Append("&upscale=false"); + } + + if (options.AnimationProcessMode != null) + { + imageProcessorUrl.Append("&animationprocessmode=" + options.AnimationProcessMode); + } + + if (options.FurtherOptions != null) + { + imageProcessorUrl.Append(options.FurtherOptions); + } + + //If furtherOptions contains a format, we need to put the quality after the format. + if (options.Quality != null && hasFormat) + { + imageProcessorUrl.Append("&quality=" + options.Quality); + } + + if (options.CacheBusterValue != null) + { + imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue); + } + + return imageProcessorUrl.ToString(); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 7134fe8703..28cc70f776 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; @@ -30,18 +31,31 @@ namespace Umbraco.Web.PropertyEditors private readonly HtmlImageSourceParser _imageSourceParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly HtmlLocalLinkParser _localLinkParser; + private readonly IImageUrlGenerator _imageUrlGenerator; + [Obsolete("Use the constructor which takes an IImageUrlGenerator")] public GridPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, RichTextEditorPastedImages pastedImages, HtmlLocalLinkParser localLinkParser) + : this(logger, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, Current.ImageUrlGenerator) + { + } + + public GridPropertyEditor(ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser, + IImageUrlGenerator imageUrlGenerator) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; _pastedImages = pastedImages; _localLinkParser = localLinkParser; + _imageUrlGenerator = imageUrlGenerator; } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -50,7 +64,7 @@ namespace Umbraco.Web.PropertyEditors /// Overridden to ensure that the value is validated /// /// - protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _pastedImages, _localLinkParser); + protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _pastedImages, _localLinkParser, _imageUrlGenerator); protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); @@ -61,19 +75,32 @@ namespace Umbraco.Web.PropertyEditors private readonly RichTextEditorPastedImages _pastedImages; private readonly RichTextPropertyEditor.RichTextPropertyValueEditor _richTextPropertyValueEditor; private readonly MediaPickerPropertyEditor.MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor; + private readonly IImageUrlGenerator _imageUrlGenerator; + [Obsolete("Use the constructor which takes an IImageUrlGenerator")] public GridPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, RichTextEditorPastedImages pastedImages, HtmlLocalLinkParser localLinkParser) + : this(attribute, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, Current.ImageUrlGenerator) + { + } + + public GridPropertyValueEditor(DataEditorAttribute attribute, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser, + IImageUrlGenerator imageUrlGenerator) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; _pastedImages = pastedImages; - _richTextPropertyValueEditor = new RichTextPropertyEditor.RichTextPropertyValueEditor(attribute, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages); + _richTextPropertyValueEditor = new RichTextPropertyEditor.RichTextPropertyValueEditor(attribute, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, _imageUrlGenerator); _mediaPickerPropertyValueEditor = new MediaPickerPropertyEditor.MediaPickerPropertyValueEditor(attribute); + _imageUrlGenerator = imageUrlGenerator; } /// @@ -108,7 +135,7 @@ namespace Umbraco.Web.PropertyEditors // Parse the HTML var html = rte.Value?.ToString(); - var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(html, mediaParentId, userId); + var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(html, mediaParentId, userId, _imageUrlGenerator); var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); rte.Value = editorValueWithMediaUrlsRemoved; diff --git a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs b/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs index 0b2a607f8b..e5afc44e43 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs @@ -36,7 +36,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - internal string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId) + internal string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IImageUrlGenerator imageUrlGenerator) { // Find all img's that has data-tmpimg attribute // Use HTML Agility Pack - https://html-agility-pack.net @@ -109,7 +109,7 @@ namespace Umbraco.Web.PropertyEditors if (width != int.MinValue && height != int.MinValue) { - location = $"{location}?width={width}&height={height}&mode=max"; + location = imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = location, ImageCropMode = "max", Width = width, Height = height }); } img.SetAttributeValue("src", location); diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 427e36b37a..42777f11ad 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; @@ -30,25 +31,36 @@ namespace Umbraco.Web.PropertyEditors private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; private readonly RichTextEditorPastedImages _pastedImages; + private readonly IImageUrlGenerator _imageUrlGenerator; /// /// The constructor will setup the property editor based on the attribute if one is found /// + [Obsolete("Use the constructor which takes an IImageUrlGenerator")] public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages) + : this(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, Current.ImageUrlGenerator) + { + } + + /// + /// The constructor will setup the property editor based on the attribute if one is found + /// + public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; _localLinkParser = localLinkParser; _pastedImages = pastedImages; + _imageUrlGenerator = imageUrlGenerator; } /// /// Create a custom value editor /// /// - protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser, _pastedImages); + protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser, _pastedImages, _imageUrlGenerator); protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor(); @@ -63,14 +75,16 @@ namespace Umbraco.Web.PropertyEditors private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; private readonly RichTextEditorPastedImages _pastedImages; + private readonly IImageUrlGenerator _imageUrlGenerator; - public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages) + public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; _localLinkParser = localLinkParser; _pastedImages = pastedImages; + _imageUrlGenerator = imageUrlGenerator; } /// @@ -124,7 +138,7 @@ namespace Umbraco.Web.PropertyEditors var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId); + var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId, _imageUrlGenerator); var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 1c4121da0c..203bae4854 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -38,6 +38,8 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Current = Umbraco.Web.Composing.Current; using Umbraco.Web.PropertyEditors; +using Umbraco.Core.Models; +using Umbraco.Web.Models; namespace Umbraco.Web.Runtime { @@ -190,6 +192,8 @@ namespace Umbraco.Web.Runtime composition.MediaUrlProviders() .Append(); + composition.RegisterUnique(); + composition.RegisterUnique(); composition.ContentFinders() diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index f796985d39..f08abfaf12 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -52,6 +52,6 @@ namespace Umbraco.Web.Templates [Obsolete("Use " + nameof(HtmlImageSourceParser) + "." + nameof(RichTextEditorPastedImages.FindAndPersistPastedTempImages) + " instead")] internal static string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ILogger logger) - => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId); + => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId, Current.Factory.GetInstance()); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0111a36993..6fbf7ea881 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -155,6 +155,7 @@ + @@ -223,6 +224,7 @@ + From e7248ea48f2b6a0611a61bd0b0b2debf79ebb89b Mon Sep 17 00:00:00 2001 From: Benjamin Carleski Date: Fri, 7 Feb 2020 16:37:27 -0800 Subject: [PATCH 4/7] Fix ImageCropperText parameter ordering --- src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 62c0792f7b..8f2014ccdb 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -174,7 +174,7 @@ namespace Umbraco.Tests.PropertyEditors public void GetCropUrl_CropAliasHeightRatioModeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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); + Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString); } /// @@ -184,7 +184,7 @@ namespace Umbraco.Tests.PropertyEditors public void GetCropUrl_WidthHeightRatioModeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&heightratio=0.5&width=300", urlString); } /// @@ -194,7 +194,7 @@ namespace Umbraco.Tests.PropertyEditors public void GetCropUrl_HeightWidthRatioModeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&height=150&widthratio=2", urlString); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&widthratio=2&height=150", urlString); } /// From 408ee452e1b52c0e06ee6ccac27ddf135131d1bd Mon Sep 17 00:00:00 2001 From: Benjamin Carleski Date: Sat, 8 Feb 2020 11:05:14 -0800 Subject: [PATCH 5/7] Code cleanup, added unit tests --- .../ImageProcessorImageUrlGeneratorTest.cs | 232 ++++++++++++++++++ .../PropertyEditors/ImageCropperTest.cs | 84 +++---- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../ImageCropperTemplateCoreExtensions.cs | 117 ++++----- .../Models/ImageProcessorImageUrlGenerator.cs | 79 ++---- 5 files changed, 343 insertions(+), 170 deletions(-) create mode 100644 src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs diff --git a/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs b/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs new file mode 100644 index 0000000000..ffbb0db946 --- /dev/null +++ b/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs @@ -0,0 +1,232 @@ +using System; +using System.Globalization; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Services; +using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Models; +using Umbraco.Web; +using Umbraco.Web.PropertyEditors; +using System.Text; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class ImageProcessorImageUrlGeneratorTest + { + private const string MediaPath = "/media/1005/img_0671.jpg"; + private static readonly ImageUrlGenerationOptions.CropCoordinates Crop = new ImageUrlGenerationOptions.CropCoordinates { X1 = 0.58729977382575338m, Y1 = 0.055768992440203169m, X2 = 0m, Y2 = 0.32457553600198386m }; + private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus1 = new ImageUrlGenerationOptions.FocalPointPosition { Top = 0.80827067669172936m, Left = 0.96m }; + private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus2 = new ImageUrlGenerationOptions.FocalPointPosition { Top = 0.41m, Left = 0.4275m }; + private static readonly ImageProcessorImageUrlGenerator Generator = new ImageProcessorImageUrlGenerator(); + + [Test] + public void GetCropUrl_CropAliasTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, Crop = Crop, Width = 100, Height = 100 }); + 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 = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, 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 = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, Width = 100, Height = 100 }); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=100&height=100", urlString); + } + + [Test] + public void GetCropUrlFurtherOptionsTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, 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); + } + + /// + /// Test that if a crop alias has been specified that doesn't exist the method returns null + /// + [Test] + public void GetCropUrlNullTest() + { + var urlString = Generator.GetImageUrl(null); + Assert.AreEqual(null, urlString); + } + + /// + /// Test that if a crop alias has been specified that doesn't exist the method returns null + /// + [Test] + public void GetCropUrlEmptyTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions()); + Assert.AreEqual("?mode=crop", urlString); + } + + /// + /// Test the GetCropUrl method on the ImageCropDataSet Model + /// + [Test] + public void GetBaseCropUrlFromModelTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { Crop = Crop, Width = 100, Height = 100 }); + Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + } + + /// + /// Test the height ratio mode with predefined crop dimensions + /// + [Test] + public void GetCropUrl_CropAliasHeightRatioModeTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, Crop = Crop, Width = 100, HeightRatio = 1 }); + Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString); + } + + /// + /// Test the height ratio mode with manual width/height dimensions + /// + [Test] + public void GetCropUrl_WidthHeightRatioModeTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, Width = 300, HeightRatio = 0.5m }); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&heightratio=0.5&width=300", urlString); + } + + /// + /// Test the height ratio mode with width/height dimensions + /// + [Test] + public void GetCropUrl_HeightWidthRatioModeTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, Height = 150, WidthRatio = 2 }); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&widthratio=2&height=150", urlString); + } + + /// + /// Test that if Crop mode is specified as anything other than Crop the image doesn't use the crop + /// + [Test] + public void GetCropUrl_SpecifiedCropModeTest() + { + var urlStringMin = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Min", Width = 300, Height = 150 }); + var urlStringBoxPad = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "BoxPad", Width = 300, Height = 150 }); + var urlStringPad = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Pad", Width = 300, Height = 150 }); + var urlStringMax = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Max", Width = 300, Height = 150 }); + var urlStringStretch = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Stretch", Width = 300, Height = 150 }); + + Assert.AreEqual(MediaPath + "?mode=min&width=300&height=150", urlStringMin); + Assert.AreEqual(MediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad); + Assert.AreEqual(MediaPath + "?mode=pad&width=300&height=150", urlStringPad); + Assert.AreEqual(MediaPath + "?mode=max&width=300&height=150", urlStringMax); + Assert.AreEqual(MediaPath + "?mode=stretch&width=300&height=150", urlStringStretch); + } + + /// + /// Test for upload property type + /// + [Test] + public void GetCropUrl_UploadTypeTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Crop", ImageCropAnchor = "Center", Width = 100, Height = 270 }); + Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString); + } + + /// + /// Test for preferFocalPoint when focal point is centered + /// + [Test] + public void GetCropUrl_PreferFocalPointCenter() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Width = 300, Height = 150 }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); + } + + /// + /// Test to check if height ratio is returned for a predefined crop without coordinates and focal point in centre when a width parameter is passed + /// + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + } + + /// + /// Test to check if height ratio is returned for a predefined crop without coordinates and focal point is custom when a width parameter is passed + /// + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus2, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); + Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + } + + /// + /// Test to check if crop ratio is ignored if useCropDimensions is true + /// + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus2, Width = 270, Height = 161 }); + Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString); + } + + /// + /// Test to check if width ratio is returned for a predefined crop without coordinates and focal point in centre when a height parameter is passed + /// + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Height = 200, WidthRatio = 1.6770186335403726708074534161m }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); + } + + /// + /// Test to check result when only a width parameter is passed, effectivly a resize only + /// + [Test] + public void GetCropUrl_WidthOnlyParameter() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Width = 200 }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=200", urlString); + } + + /// + /// Test to check result when only a height parameter is passed, effectivly a resize only + /// + [Test] + public void GetCropUrl_HeightOnlyParameter() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Height = 200 }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&height=200", urlString); + } + + /// + /// Test to check result when using a background color with padding + /// + [Test] + public void GetCropUrl_BackgroundColorParameter() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Pad", Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" }); + Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); + } + } +} diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 8f2014ccdb..c5c2b4e61f 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -5,9 +5,7 @@ using Newtonsoft.Json; using NUnit.Framework; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -112,7 +110,7 @@ namespace Umbraco.Tests.PropertyEditors public void GetCropUrl_CropAliasTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString); } /// @@ -122,28 +120,28 @@ namespace Umbraco.Tests.PropertyEditors public void GetCropUrl_CropAliasIgnoreWidthHeightTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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); + Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString); } [Test] public void GetCropUrl_WidthHeightTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 200, height: 300); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300", urlString); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=200&h=300", urlString); } [Test] public void GetCropUrl_FocalPointTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "thumb", preferFocalPoint: true, useCropDimensions: true); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=100&height=100", urlString); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=100&h=100", urlString); } [Test] public void GetCropUrlFurtherOptionsTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 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); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=200&h=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); } /// @@ -164,7 +162,7 @@ namespace Umbraco.Tests.PropertyEditors { var cropDataSet = CropperJson1.DeserializeImageCropperValue(); var urlString = cropDataSet.GetCropUrl("thumb", new TestImageUrlGenerator()); - Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + Assert.AreEqual("?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString); } /// @@ -174,7 +172,7 @@ namespace Umbraco.Tests.PropertyEditors public void GetCropUrl_CropAliasHeightRatioModeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode:ImageCropRatioMode.Height); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString); + Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&hr=1&w=100", urlString); } /// @@ -184,7 +182,7 @@ namespace Umbraco.Tests.PropertyEditors public void GetCropUrl_WidthHeightRatioModeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode:ImageCropRatioMode.Height); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&heightratio=0.5&width=300", urlString); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&hr=0.5&w=300", urlString); } /// @@ -194,7 +192,7 @@ namespace Umbraco.Tests.PropertyEditors public void GetCropUrl_HeightWidthRatioModeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&widthratio=2&height=150", urlString); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&wr=2&h=150", urlString); } /// @@ -209,11 +207,11 @@ namespace Umbraco.Tests.PropertyEditors var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode:ImageCropMode.Max); var urlStringStretch = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Stretch); - Assert.AreEqual(MediaPath + "?mode=min&width=300&height=150", urlStringMin); - Assert.AreEqual(MediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad); - Assert.AreEqual(MediaPath + "?mode=pad&width=300&height=150", urlStringPad); - Assert.AreEqual(MediaPath + "?mode=max&width=300&height=150", urlString); - Assert.AreEqual(MediaPath + "?mode=stretch&width=300&height=150", urlStringStretch); + Assert.AreEqual(MediaPath + "?m=min&w=300&h=150", urlStringMin); + Assert.AreEqual(MediaPath + "?m=boxpad&w=300&h=150", urlStringBoxPad); + Assert.AreEqual(MediaPath + "?m=pad&w=300&h=150", urlStringPad); + Assert.AreEqual(MediaPath + "?m=max&w=300&h=150", urlString); + Assert.AreEqual(MediaPath + "?m=stretch&w=300&h=150", urlStringStretch); } /// @@ -223,7 +221,7 @@ namespace Umbraco.Tests.PropertyEditors public void GetCropUrl_UploadTypeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), width: 100, height: 270, imageCropMode: ImageCropMode.Crop, imageCropAnchor: ImageCropAnchor.Center); - Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString); + Assert.AreEqual(MediaPath + "?m=crop&a=center&w=100&h=270", urlString); } /// @@ -235,7 +233,7 @@ namespace Umbraco.Tests.PropertyEditors const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"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}}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 300, height: 150, preferFocalPoint:true); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); + Assert.AreEqual(MediaPath + "?m=defaultcrop&w=300&h=150", urlString); } /// @@ -247,7 +245,7 @@ namespace Umbraco.Tests.PropertyEditors const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + Assert.AreEqual(MediaPath + "?m=defaultcrop&hr=0.5962962962962962962962962963&w=200", urlString); } /// @@ -259,7 +257,7 @@ namespace Umbraco.Tests.PropertyEditors const string cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200); - Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + Assert.AreEqual(MediaPath + "?f=0.41x0.4275&hr=0.5962962962962962962962962963&w=200", urlString); } /// @@ -271,7 +269,7 @@ namespace Umbraco.Tests.PropertyEditors const string cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200, useCropDimensions: true); - Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString); + Assert.AreEqual(MediaPath + "?f=0.41x0.4275&w=270&h=161", urlString); } /// @@ -283,7 +281,7 @@ namespace Umbraco.Tests.PropertyEditors const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", height: 200); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); + Assert.AreEqual(MediaPath + "?m=defaultcrop&wr=1.6770186335403726708074534161&h=200", urlString); } /// @@ -295,7 +293,7 @@ namespace Umbraco.Tests.PropertyEditors const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 200); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=200", urlString); + Assert.AreEqual(MediaPath + "?m=defaultcrop&w=200", urlString); } /// @@ -307,7 +305,7 @@ namespace Umbraco.Tests.PropertyEditors const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, height: 200); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&height=200", urlString); + Assert.AreEqual(MediaPath + "?m=defaultcrop&h=200", urlString); } /// @@ -319,10 +317,10 @@ namespace Umbraco.Tests.PropertyEditors var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"" + MediaPath + "\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, furtherOptions: "&bgcolor=fff"); - Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); + Assert.AreEqual(MediaPath + "?m=pad&w=400&h=400&bgcolor=fff", urlString); } - private class TestImageUrlGenerator : IImageUrlGenerator + internal class TestImageUrlGenerator : IImageUrlGenerator { public string GetImageUrl(ImageUrlGenerationOptions options) { @@ -330,42 +328,40 @@ namespace Umbraco.Tests.PropertyEditors if (options.FocalPoint != null) { - imageProcessorUrl.Append("?center="); + imageProcessorUrl.Append("?f="); imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append(","); + imageProcessorUrl.Append("x"); imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("&mode=crop"); } else if (options.Crop != null) { - imageProcessorUrl.Append("?crop="); + imageProcessorUrl.Append("?c="); imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("&cropmode=percentage"); } else if (options.DefaultCrop) { - imageProcessorUrl.Append("?anchor=center&mode=crop"); + imageProcessorUrl.Append("?m=defaultcrop"); } else { - imageProcessorUrl.Append("?mode=" + options.ImageCropMode.ToString().ToLower()); - if (options.ImageCropAnchor != null)imageProcessorUrl.Append("&anchor=" + options.ImageCropAnchor.ToString().ToLower()); + imageProcessorUrl.Append("?m=" + options.ImageCropMode.ToString().ToLower()); + if (options.ImageCropAnchor != null)imageProcessorUrl.Append("&a=" + options.ImageCropAnchor.ToString().ToLower()); } - var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); - if (options.Quality != null && hasFormat == false) imageProcessorUrl.Append("&quality=" + options.Quality); - if (options.HeightRatio != null) imageProcessorUrl.Append("&heightratio=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); - if (options.WidthRatio != null) imageProcessorUrl.Append("&widthratio=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); - if (options.Width != null) imageProcessorUrl.Append("&width=" + options.Width); - if (options.Height != null) imageProcessorUrl.Append("&height=" + options.Height); - if (options.UpScale == false) imageProcessorUrl.Append("&upscale=false"); - if (options.AnimationProcessMode != null) imageProcessorUrl.Append("&animationprocessmode=" + options.AnimationProcessMode); + var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&f="); + if (options.Quality != null && hasFormat == false) imageProcessorUrl.Append("&q=" + options.Quality); + if (options.HeightRatio != null) imageProcessorUrl.Append("&hr=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.WidthRatio != null) imageProcessorUrl.Append("&wr=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.Width != null) imageProcessorUrl.Append("&w=" + options.Width); + if (options.Height != null) imageProcessorUrl.Append("&h=" + options.Height); + if (options.UpScale == false) imageProcessorUrl.Append("&u=no"); + if (options.AnimationProcessMode != null) imageProcessorUrl.Append("&apm=" + options.AnimationProcessMode); if (options.FurtherOptions != null) imageProcessorUrl.Append(options.FurtherOptions); - if (options.Quality != null && hasFormat) imageProcessorUrl.Append("&quality=" + options.Quality); - if (options.CacheBusterValue != null) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue); + if (options.Quality != null && hasFormat) imageProcessorUrl.Append("&q=" + options.Quality); + if (options.CacheBusterValue != null) imageProcessorUrl.Append("&r=").Append(options.CacheBusterValue); return imageProcessorUrl.ToString(); } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ff923bb04b..f9afdaa040 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -140,6 +140,7 @@ + diff --git a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs index b8e832beb9..54b4ce2856 100644 --- a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs @@ -308,82 +308,71 @@ namespace Umbraco.Web ImageCropRatioMode? ratioMode = null, bool upScale = true) { - if (string.IsNullOrEmpty(imageUrl) == false) + if (string.IsNullOrEmpty(imageUrl)) return string.Empty; + + ImageUrlGenerationOptions options; + + if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) { - ImageUrlGenerationOptions options; + var crop = cropDataSet.GetCrop(cropAlias); - if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) + // if a crop was specified, but not found, return null + if (crop == null && !string.IsNullOrWhiteSpace(cropAlias)) + return null; + + options = cropDataSet.GetCropBaseOptions(imageUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint); + + if (crop != null & useCropDimensions) { - var crop = cropDataSet.GetCrop(cropAlias); - - // if a crop was specified, but not found, return null - if (crop == null && !string.IsNullOrWhiteSpace(cropAlias)) - return null; - - options = cropDataSet.GetCropBaseOptions(imageUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint); - - 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) - { - options.HeightRatio = (decimal)crop.Height / crop.Width; - } - - // 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) - { - options.WidthRatio = (decimal)crop.Width / crop.Height; - } - } - else - { - options = new ImageUrlGenerationOptions - { - ImageUrl = imageUrl, - ImageCropMode = (imageCropMode ?? ImageCropMode.Pad).ToString().ToLowerInvariant(), - ImageCropAnchor = imageCropAnchor?.ToString().ToLowerInvariant() - }; + width = crop.Width; + height = crop.Height; } - options.Quality = quality; - options.Width = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Width ? null : width; - options.Height = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Height ? null : height; - - if (ratioMode == ImageCropRatioMode.Width && height != 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) { - // if only height specified then assume a square - if (width == null) - { - width = height; - } - - options.WidthRatio = (decimal)width / (decimal)height; + options.HeightRatio = (decimal)crop.Height / crop.Width; } - if (ratioMode == ImageCropRatioMode.Height && width != null) + // 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) { - // if only width specified then assume a square - if (height == null) - { - height = width; - } - - options.HeightRatio = (decimal)height / (decimal)width; + options.WidthRatio = (decimal)crop.Width / crop.Height; } - - options.UpScale = upScale; - options.FurtherOptions = furtherOptions; - options.CacheBusterValue = cacheBusterValue; - - return imageUrlGenerator.GetImageUrl(options); + } + else + { + options = new ImageUrlGenerationOptions + { + ImageUrl = imageUrl, + ImageCropMode = (imageCropMode ?? ImageCropMode.Pad).ToString().ToLowerInvariant(), + ImageCropAnchor = imageCropAnchor?.ToString().ToLowerInvariant() + }; } - return string.Empty; + options.Quality = quality; + options.Width = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Width ? null : width; + options.Height = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Height ? null : height; + + if (ratioMode == ImageCropRatioMode.Width && height != null) + { + // if only height specified then assume a square + if (width == null) width = height; + options.WidthRatio = (decimal)width / (decimal)height; + } + + if (ratioMode == ImageCropRatioMode.Height && width != null) + { + // if only width specified then assume a square + if (height == null) height = width; + options.HeightRatio = (decimal)height / (decimal)width; + } + + options.UpScale = upScale; + options.FurtherOptions = furtherOptions; + options.CacheBusterValue = cacheBusterValue; + + return imageUrlGenerator.GetImageUrl(options); } } } diff --git a/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs b/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs index 816cfda0e9..47d7488c10 100644 --- a/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs +++ b/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; +using System.Globalization; using System.Text; -using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Models; @@ -13,13 +9,14 @@ namespace Umbraco.Web.Models { public string GetImageUrl(ImageUrlGenerationOptions options) { + if (options == null) return null; + var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty); if (options.FocalPoint != null) { imageProcessorUrl.Append("?center="); - imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append(","); + imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)).Append(","); imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); imageProcessorUrl.Append("&mode=crop"); } @@ -34,73 +31,31 @@ namespace Umbraco.Web.Models } else if (options.DefaultCrop) { - imageProcessorUrl.Append("?anchor=center"); - imageProcessorUrl.Append("&mode=crop"); + imageProcessorUrl.Append("?anchor=center&mode=crop"); } else { - imageProcessorUrl.Append("?mode=" + options.ImageCropMode.ToString().ToLower()); + imageProcessorUrl.Append("?mode=").Append((options.ImageCropMode ?? "crop").ToLower()); - if (options.ImageCropAnchor != null) - { - imageProcessorUrl.Append("&anchor=" + options.ImageCropAnchor.ToString().ToLower()); - } + if (options.ImageCropAnchor != null) imageProcessorUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToLower()); } var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); //Only put quality here, if we don't have a format specified. //Otherwise we need to put quality at the end to avoid it being overridden by the format. - if (options.Quality != null && hasFormat == false) - { - imageProcessorUrl.Append("&quality=" + options.Quality); - } - - if (options.HeightRatio != null) - { - imageProcessorUrl.Append("&heightratio=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (options.WidthRatio != null) - { - imageProcessorUrl.Append("&widthratio=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (options.Width != null) - { - imageProcessorUrl.Append("&width=" + options.Width); - } - - if (options.Height != null) - { - imageProcessorUrl.Append("&height=" + options.Height); - } - - if (options.UpScale == false) - { - imageProcessorUrl.Append("&upscale=false"); - } - - if (options.AnimationProcessMode != null) - { - imageProcessorUrl.Append("&animationprocessmode=" + options.AnimationProcessMode); - } - - if (options.FurtherOptions != null) - { - imageProcessorUrl.Append(options.FurtherOptions); - } + if (options.Quality != null && hasFormat == false) imageProcessorUrl.Append("&quality=").Append(options.Quality); + if (options.HeightRatio != null) imageProcessorUrl.Append("&heightratio=").Append(options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.WidthRatio != null) imageProcessorUrl.Append("&widthratio=").Append(options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.Width != null) imageProcessorUrl.Append("&width=").Append(options.Width); + if (options.Height != null) imageProcessorUrl.Append("&height=").Append(options.Height); + if (options.UpScale == false) imageProcessorUrl.Append("&upscale=false"); + if (options.AnimationProcessMode != null) imageProcessorUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode); + if (options.FurtherOptions != null) imageProcessorUrl.Append(options.FurtherOptions); //If furtherOptions contains a format, we need to put the quality after the format. - if (options.Quality != null && hasFormat) - { - imageProcessorUrl.Append("&quality=" + options.Quality); - } - - if (options.CacheBusterValue != null) - { - imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue); - } + if (options.Quality != null && hasFormat) imageProcessorUrl.Append("&quality=").Append(options.Quality); + if (options.CacheBusterValue != null) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue); return imageProcessorUrl.ToString(); } From 7e7d38e74b68e28aed8aeae9fd9d44417d90a075 Mon Sep 17 00:00:00 2001 From: Benjamin Carleski Date: Sat, 8 Feb 2020 16:49:12 -0800 Subject: [PATCH 6/7] Add ImageUrlGeneratorController --- .../resources/imageurlgenerator.resource.js | 36 ++++++++++++++++++ .../Editors/BackOfficeServerVariables.cs | 4 ++ .../Editors/ImageUrlGeneratorController.cs | 38 +++++++++++++++++++ .../ImageCropperTemplateCoreExtensions.cs | 4 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js create mode 100644 src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js new file mode 100644 index 0000000000..a937cd2675 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js @@ -0,0 +1,36 @@ +/** + * @ngdoc service + * @name umbraco.resources.imageUrlGeneratorResource + * @function + * + * @description + * Used by the various controllers to get an image URL formatted correctly for the current image URL generator + */ +(function () { + 'use strict'; + + function imageUrlGeneratorResource($http, umbRequestHelper) { + + function getCropUrl(mediaPath, width, height, imageCropMode, animationProcessMode) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "imageUrlGeneratorApiBaseUrl", + "GetCropUrl", + { mediaPath, width, height, imageCropMode, animationProcessMode })), + 'Failed to get crop URL'); + } + + + var resource = { + getCropUrl: getCropUrl + }; + + return resource; + + } + + angular.module('umbraco.resources').factory('imageUrlGeneratorResource', imageUrlGeneratorResource); + +})(); diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index dc46a97df4..89413db84d 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -314,6 +314,10 @@ namespace Umbraco.Web.Editors "tinyMceApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.UploadImage()) }, + { + "imageUrlGeneratorApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetCropUrl(null, null, null, null, null)) + }, } }, { diff --git a/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs b/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs new file mode 100644 index 0000000000..7a02b2bd65 --- /dev/null +++ b/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Models; +using Umbraco.Web.Models; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.Editors +{ + /// + /// The API controller used for getting URLs for images with parameters + /// + /// + /// + /// This controller allows for retrieving URLs for processed images, such as resized, cropped, + /// or otherwise altered. These can be different based on the IImageUrlGenerator + /// implementation in use, and so back-office could should not rely on hard-coded string + /// building to generate correct URLs + /// + /// + [PluginController("UmbracoApi")] + public class ImageUrlGeneratorController + { + private readonly IImageUrlGenerator _imageUrlGenerator; + + public ImageUrlGeneratorController(IImageUrlGenerator imageUrlGenerator) + { + _imageUrlGenerator = imageUrlGenerator; + } + + public string GetCropUrl(string mediaPath, int? width = null, int? height = null, ImageCropMode? imageCropMode = null, string animationProcessMode = null) + { + return mediaPath.GetCropUrl(_imageUrlGenerator, null, width: width, height: height, imageCropMode: imageCropMode, animationProcessMode: animationProcessMode); + } + } +} diff --git a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs index 54b4ce2856..74b32f2941 100644 --- a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs @@ -306,7 +306,8 @@ namespace Umbraco.Web string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) + bool upScale = true, + string animationProcessMode = null) { if (string.IsNullOrEmpty(imageUrl)) return string.Empty; @@ -353,6 +354,7 @@ namespace Umbraco.Web options.Quality = quality; options.Width = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Width ? null : width; options.Height = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Height ? null : height; + options.AnimationProcessMode = animationProcessMode; if (ratioMode == ImageCropRatioMode.Width && height != null) { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6fbf7ea881..e5005f0fc0 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -150,6 +150,7 @@ + From 43094891742f9d4ff0110f30e32f7973f068f59a Mon Sep 17 00:00:00 2001 From: Benjamin Carleski Date: Sun, 9 Feb 2020 11:12:29 -0800 Subject: [PATCH 7/7] Make changes for PR suggestions --- .../Models/ImageUrlGenerationOptions.cs | 45 +++++++++++++--- src/Umbraco.Core/Models/UserExtensions.cs | 10 ++-- .../ValueConverters/ImageCropperValue.cs | 6 +-- .../ImageProcessorImageUrlGeneratorTest.cs | 52 +++++++++---------- .../Editors/ImageUrlGeneratorController.cs | 5 +- src/Umbraco.Web/Editors/ImagesController.cs | 2 +- .../ImageCropperTemplateCoreExtensions.cs | 3 +- .../Models/ImageProcessorImageUrlGenerator.cs | 41 ++++++++------- .../RichTextEditorPastedImages.cs | 2 +- 9 files changed, 98 insertions(+), 68 deletions(-) diff --git a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs index 9e8d3f3a00..f87657c33d 100644 --- a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -1,8 +1,17 @@ namespace Umbraco.Core.Models { + /// + /// These are options that are passed to the IImageUrlGenerator implementation to determine + /// the propery URL that is needed + /// public class ImageUrlGenerationOptions { - public string ImageUrl { get; set; } + public ImageUrlGenerationOptions (string imageUrl) + { + ImageUrl = imageUrl; + } + + public string ImageUrl { get; } public int? Width { get; set; } public int? Height { get; set; } public decimal? WidthRatio { get; set; } @@ -18,18 +27,40 @@ public bool UpScale { get; set; } = true; public string AnimationProcessMode { get; set; } + /// + /// The focal point position, in whatever units the registered IImageUrlGenerator uses, + /// typically a percentage of the total image from 0.0 to 1.0. + /// public class FocalPointPosition { - public decimal Left { get; set; } - public decimal Top { get; set; } + public FocalPointPosition (decimal top, decimal left) + { + Left = left; + Top = top; + } + + public decimal Left { get; } + public decimal Top { get; } } + /// + /// The bounds of the crop within the original image, in whatever units the registered + /// IImageUrlGenerator uses, typically a percentage between 0 and 100. + /// public class CropCoordinates { - public decimal X1 { get; set; } - public decimal Y1 { get; set; } - public decimal X2 { get; set; } - public decimal Y2 { get; set; } + public CropCoordinates (decimal x1, decimal y1, decimal x2, decimal y2) + { + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + } + + public decimal X1 { get; } + public decimal Y1 { get; } + public decimal X2 { get; } + public decimal Y2 { get; } } } } diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index f11a8efe46..5be66bac47 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -109,11 +109,11 @@ namespace Umbraco.Core.Models var urlGenerator = Current.ImageUrlGenerator; return new[] { - urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 30, Height = 30 }), - urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 60, Height = 60 }), - urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 90, Height = 90 }), - urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 150, Height = 150 }), - urlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = avatarUrl, ImageCropMode = "crop", Width = 300, Height = 300 }) + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 30, Height = 30 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 60, Height = 60 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 90, Height = 90 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 150, Height = 150 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 300, Height = 300 }) }; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs index 70eb7e0477..ab217d3870 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -67,15 +67,15 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters || crop != null && crop.Coordinates == null && HasFocalPoint() || defaultCrop && HasFocalPoint()) { - return new ImageUrlGenerationOptions { ImageUrl = url, FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition { Left = FocalPoint.Left, Top = FocalPoint.Top } }; + return new ImageUrlGenerationOptions(url) { FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(FocalPoint.Top, FocalPoint.Left) }; } else if (crop != null && crop.Coordinates != null && preferFocalPoint == false) { - return new ImageUrlGenerationOptions { ImageUrl = url, Crop = new ImageUrlGenerationOptions.CropCoordinates { X1 = crop.Coordinates.X1, X2 = crop.Coordinates.X2, Y1 = crop.Coordinates.Y1, Y2 = crop.Coordinates.Y2 } }; + return new ImageUrlGenerationOptions(url) { Crop = new ImageUrlGenerationOptions.CropCoordinates(crop.Coordinates.X1, crop.Coordinates.Y1, crop.Coordinates.X2, crop.Coordinates.Y2) }; } else { - return new ImageUrlGenerationOptions { ImageUrl = url, DefaultCrop = true }; + return new ImageUrlGenerationOptions(url) { DefaultCrop = true }; } } diff --git a/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs b/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs index ffbb0db946..30ead90de9 100644 --- a/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs +++ b/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs @@ -27,36 +27,36 @@ namespace Umbraco.Tests.Models public class ImageProcessorImageUrlGeneratorTest { private const string MediaPath = "/media/1005/img_0671.jpg"; - private static readonly ImageUrlGenerationOptions.CropCoordinates Crop = new ImageUrlGenerationOptions.CropCoordinates { X1 = 0.58729977382575338m, Y1 = 0.055768992440203169m, X2 = 0m, Y2 = 0.32457553600198386m }; - private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus1 = new ImageUrlGenerationOptions.FocalPointPosition { Top = 0.80827067669172936m, Left = 0.96m }; - private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus2 = new ImageUrlGenerationOptions.FocalPointPosition { Top = 0.41m, Left = 0.4275m }; + private static readonly ImageUrlGenerationOptions.CropCoordinates Crop = new ImageUrlGenerationOptions.CropCoordinates(0.58729977382575338m, 0.055768992440203169m, 0m, 0.32457553600198386m); + private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus1 = new ImageUrlGenerationOptions.FocalPointPosition(0.80827067669172936m, 0.96m); + private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus2 = new ImageUrlGenerationOptions.FocalPointPosition(0.41m, 0.4275m); private static readonly ImageProcessorImageUrlGenerator Generator = new ImageProcessorImageUrlGenerator(); [Test] public void GetCropUrl_CropAliasTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, Crop = Crop, Width = 100, Height = 100 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = Crop, Width = 100, Height = 100 }); 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 = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, Width = 200, Height = 300 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, 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 = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, Width = 100, Height = 100 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 100, Height = 100 }); Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=100&height=100", urlString); } [Test] public void GetCropUrlFurtherOptionsTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, Width = 200, Height = 300, FurtherOptions = "&filter=comic&roundedcorners=radius-26|bgcolor-fff" }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, 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); } @@ -76,7 +76,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrlEmptyTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions()); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(null)); Assert.AreEqual("?mode=crop", urlString); } @@ -86,7 +86,7 @@ namespace Umbraco.Tests.Models [Test] public void GetBaseCropUrlFromModelTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { Crop = Crop, Width = 100, Height = 100 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(null) { Crop = Crop, Width = 100, Height = 100 }); Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } @@ -96,7 +96,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_CropAliasHeightRatioModeTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, Crop = Crop, Width = 100, HeightRatio = 1 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = Crop, Width = 100, HeightRatio = 1 }); Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString); } @@ -106,7 +106,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_WidthHeightRatioModeTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, Width = 300, HeightRatio = 0.5m }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 300, HeightRatio = 0.5m }); Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&heightratio=0.5&width=300", urlString); } @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_HeightWidthRatioModeTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus1, Height = 150, WidthRatio = 2 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Height = 150, WidthRatio = 2 }); Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&widthratio=2&height=150", urlString); } @@ -126,11 +126,11 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_SpecifiedCropModeTest() { - var urlStringMin = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Min", Width = 300, Height = 150 }); - var urlStringBoxPad = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "BoxPad", Width = 300, Height = 150 }); - var urlStringPad = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Pad", Width = 300, Height = 150 }); - var urlStringMax = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Max", Width = 300, Height = 150 }); - var urlStringStretch = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Stretch", Width = 300, Height = 150 }); + var urlStringMin = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Min", Width = 300, Height = 150 }); + var urlStringBoxPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "BoxPad", Width = 300, Height = 150 }); + var urlStringPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Pad", Width = 300, Height = 150 }); + var urlStringMax = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Max", Width = 300, Height = 150 }); + var urlStringStretch = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Stretch", Width = 300, Height = 150 }); Assert.AreEqual(MediaPath + "?mode=min&width=300&height=150", urlStringMin); Assert.AreEqual(MediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad); @@ -145,7 +145,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_UploadTypeTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Crop", ImageCropAnchor = "Center", Width = 100, Height = 270 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Crop", ImageCropAnchor = "Center", Width = 100, Height = 270 }); Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString); } @@ -155,7 +155,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_PreferFocalPointCenter() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Width = 300, Height = 150 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 300, Height = 150 }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); } @@ -165,7 +165,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); } @@ -175,7 +175,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus2, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus2, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); } @@ -185,7 +185,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, FocalPoint = Focus2, Width = 270, Height = 161 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus2, Width = 270, Height = 161 }); Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString); } @@ -195,7 +195,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Height = 200, WidthRatio = 1.6770186335403726708074534161m }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200, WidthRatio = 1.6770186335403726708074534161m }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); } @@ -205,7 +205,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_WidthOnlyParameter() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Width = 200 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200 }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=200", urlString); } @@ -215,7 +215,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_HeightOnlyParameter() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, DefaultCrop = true, Height = 200 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200 }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&height=200", urlString); } @@ -225,7 +225,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_BackgroundColorParameter() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = MediaPath, ImageCropMode = "Pad", Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Pad", Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" }); Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); } } diff --git a/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs b/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs index 7a02b2bd65..87d7e29619 100644 --- a/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs +++ b/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs @@ -16,12 +16,11 @@ namespace Umbraco.Web.Editors /// /// This controller allows for retrieving URLs for processed images, such as resized, cropped, /// or otherwise altered. These can be different based on the IImageUrlGenerator - /// implementation in use, and so back-office could should not rely on hard-coded string + /// implementation in use, and so the BackOffice could should not rely on hard-coded string /// building to generate correct URLs /// /// - [PluginController("UmbracoApi")] - public class ImageUrlGeneratorController + public class ImageUrlGeneratorController : UmbracoAuthorizedJsonController { private readonly IImageUrlGenerator _imageUrlGenerator; diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index c8f0e2d9bf..f3682a5b43 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -84,7 +84,7 @@ namespace Umbraco.Web.Editors } var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null; - var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = imagePath, UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = "max", CacheBusterValue = rnd }); + var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) { UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = "max", CacheBusterValue = rnd }); response.Headers.Location = new Uri(imageUrl, UriKind.RelativeOrAbsolute); return response; diff --git a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs index 74b32f2941..7ac5578d75 100644 --- a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs @@ -343,9 +343,8 @@ namespace Umbraco.Web } else { - options = new ImageUrlGenerationOptions + options = new ImageUrlGenerationOptions (imageUrl) { - ImageUrl = imageUrl, ImageCropMode = (imageCropMode ?? ImageCropMode.Pad).ToString().ToLowerInvariant(), ImageCropAnchor = imageCropAnchor?.ToString().ToLowerInvariant() }; diff --git a/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs b/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs index 47d7488c10..e471e9aa4a 100644 --- a/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs +++ b/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs @@ -13,26 +13,9 @@ namespace Umbraco.Web.Models var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty); - if (options.FocalPoint != null) - { - imageProcessorUrl.Append("?center="); - imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("&mode=crop"); - } - else if (options.Crop != null) - { - imageProcessorUrl.Append("?crop="); - imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("&cropmode=percentage"); - } - else if (options.DefaultCrop) - { - imageProcessorUrl.Append("?anchor=center&mode=crop"); - } + if (options.FocalPoint != null) AppendFocalPoint(imageProcessorUrl, options); + else if (options.Crop != null) AppendCrop(imageProcessorUrl, options); + else if (options.DefaultCrop) imageProcessorUrl.Append("?anchor=center&mode=crop"); else { imageProcessorUrl.Append("?mode=").Append((options.ImageCropMode ?? "crop").ToLower()); @@ -59,5 +42,23 @@ namespace Umbraco.Web.Models return imageProcessorUrl.ToString(); } + + private void AppendFocalPoint(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) + { + imageProcessorUrl.Append("?center="); + imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&mode=crop"); + } + + private void AppendCrop(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) + { + imageProcessorUrl.Append("?crop="); + imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&cropmode=percentage"); + } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs b/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs index e5afc44e43..e7ce0763e4 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs @@ -109,7 +109,7 @@ namespace Umbraco.Web.PropertyEditors if (width != int.MinValue && height != int.MinValue) { - location = imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions { ImageUrl = location, ImageCropMode = "max", Width = width, Height = height }); + location = imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(location) { ImageCropMode = "max", Width = width, Height = height }); } img.SetAttributeValue("src", location);