Introduce Image URL Generator abstraction

This commit is contained in:
Benjamin Carleski
2020-02-07 15:01:03 -08:00
parent 02a9645ed1
commit a7ff20de6e
22 changed files with 747 additions and 269 deletions

View File

@@ -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<IVariationContextAccessor>();
public static IImageUrlGenerator ImageUrlGenerator
=> Factory.GetInstance<IImageUrlGenerator>();
#endregion
}
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Core.Models
{
public interface IImageUrlGenerator
{
string GetImageUrl(ImageUrlGenerationOptions options);
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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 })
};
}

View File

@@ -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 };
}
}
/// <summary>
/// Gets the value image url for a specified crop.
/// </summary>
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);
/// <summary>
/// Gets the value image url for a specified crop.
/// </summary>
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);
}
/// <summary>
/// Gets the value image url for a specific width and height.
/// </summary>
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);
/// <summary>
/// Gets the value image url for a specific width and height.
/// </summary>
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);
}
/// <summary>

View File

@@ -132,6 +132,8 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyDataDto80.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyTypeDto80.cs" />
<Compile Include="Migrations\Upgrade\V_8_6_0\AddMainDomLock.cs" />
<Compile Include="Models\IImageUrlGenerator.cs" />
<Compile Include="Models\ImageUrlGenerationOptions.cs" />
<Compile Include="Runtime\IMainDomLock.cs" />
<Compile Include="Runtime\MainDomSemaphoreLock.cs" />
<Compile Include="Runtime\SqlMainDomLock.cs" />

View File

@@ -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();
}
}
}
}

View File

@@ -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<IMediaService>(), Mock.Of<IContentTypeBaseServiceProvider>());
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<IImageUrlGenerator>())) { Id = 1 });
var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), converters, dataTypeService);

View File

@@ -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<IImageUrlGenerator>())) { Id = 1002 },
new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 },
new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 },
new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 });

View File

@@ -97,6 +97,7 @@ namespace Umbraco.Tests.Runtimes
composition.Register<IVariationContextAccessor, TestVariationContextAccessor>(Lifetime.Singleton);
composition.Register<IDefaultCultureAccessor, TestDefaultCultureAccessor>(Lifetime.Singleton);
composition.Register<ISiteDomainHelper>(_ => Mock.Of<ISiteDomainHelper>(), Lifetime.Singleton);
composition.Register(_ => Mock.Of<IImageUrlGenerator>(), Lifetime.Singleton);
composition.RegisterUnique(f => new DistributedCache());
composition.WithCollectionBuilder<UrlProviderCollectionBuilder>().Append<DefaultUrlProvider>();
composition.RegisterUnique<IDistributedCacheBinder, DistributedCacheBinder>();

View File

@@ -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<IRuntimeState>();
runtimeStateMock.Setup(x => x.Level).Returns(RuntimeLevel.Run);
Composition.RegisterUnique(f => runtimeStateMock.Object);
Composition.Register(_ => Mock.Of<IImageUrlGenerator>());
// ah...
Composition.WithCollectionBuilder<ActionCollectionBuilder>();

View File

@@ -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 += "&center=" + 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;
<img src="@url" alt="@altText">
if (Model.value.caption != null)

View File

@@ -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;
}
/// <summary>
@@ -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;
}

View File

@@ -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
{
/// <summary>
/// Gets the ImageProcessor Url by the crop alias (from the "umbracoFile" property alias) on the IPublishedContent item
/// </summary>
/// <param name="mediaItem">
/// The IPublishedContent item.
/// </param>
/// <param name="cropAlias">
/// The crop alias e.g. thumbnail
/// </param>
/// <returns>
/// The ImageProcessor.Web Url.
/// </returns>
public static string GetCropUrl(this IPublishedContent mediaItem, string cropAlias, IImageUrlGenerator imageUrlGenerator)
{
return mediaItem.GetCropUrl(imageUrlGenerator, cropAlias: cropAlias, useCropDimensions: true);
}
/// <summary>
/// Gets the ImageProcessor Url by the crop alias using the specified property containing the image cropper Json data on the IPublishedContent item.
/// </summary>
/// <param name="mediaItem">
/// The IPublishedContent item.
/// </param>
/// <param name="propertyAlias">
/// The property alias of the property containing the Json data e.g. umbracoFile
/// </param>
/// <param name="cropAlias">
/// The crop alias e.g. thumbnail
/// </param>
/// <returns>
/// The ImageProcessor.Web Url.
/// </returns>
public static string GetCropUrl(this IPublishedContent mediaItem, string propertyAlias, string cropAlias, IImageUrlGenerator imageUrlGenerator)
{
return mediaItem.GetCropUrl(imageUrlGenerator, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true);
}
/// <summary>
/// Gets the ImageProcessor Url from the IPublishedContent item.
/// </summary>
/// <param name="mediaItem">
/// The IPublishedContent item.
/// </param>
/// <param name="width">
/// The width of the output image.
/// </param>
/// <param name="height">
/// The height of the output image.
/// </param>
/// <param name="propertyAlias">
/// Property alias of the property containing the Json data.
/// </param>
/// <param name="cropAlias">
/// The crop alias.
/// </param>
/// <param name="quality">
/// Quality percentage of the output image.
/// </param>
/// <param name="imageCropMode">
/// The image crop mode.
/// </param>
/// <param name="imageCropAnchor">
/// The image crop anchor.
/// </param>
/// <param name="preferFocalPoint">
/// Use focal point, to generate an output image using the focal point instead of the predefined crop
/// </param>
/// <param name="useCropDimensions">
/// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters.
/// </param>
/// <param name="cacheBuster">
/// Add a serialized date of the last edit of the item to ensure client cache refresh when updated
/// </param>
/// <param name="furtherOptions">
/// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example:
/// <example>
/// <![CDATA[
/// furtherOptions: "&bgcolor=fff"
/// ]]>
/// </example>
/// </param>
/// <param name="ratioMode">
/// Use a dimension as a ratio
/// </param>
/// <param name="upScale">
/// If the image should be upscaled to requested dimensions
/// </param>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
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<ImageCropperValue>();
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);
}
/// <summary>
/// Gets the ImageProcessor Url from the image path.
/// </summary>
/// <param name="imageUrl">
/// The image url.
/// </param>
/// <param name="width">
/// The width of the output image.
/// </param>
/// <param name="height">
/// The height of the output image.
/// </param>
/// <param name="imageCropperValue">
/// The Json data from the Umbraco Core Image Cropper property editor
/// </param>
/// <param name="cropAlias">
/// The crop alias.
/// </param>
/// <param name="quality">
/// Quality percentage of the output image.
/// </param>
/// <param name="imageCropMode">
/// The image crop mode.
/// </param>
/// <param name="imageCropAnchor">
/// The image crop anchor.
/// </param>
/// <param name="preferFocalPoint">
/// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one
/// </param>
/// <param name="useCropDimensions">
/// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters
/// </param>
/// <param name="cacheBusterValue">
/// Add a serialized date of the last edit of the item to ensure client cache refresh when updated
/// </param>
/// <param name="furtherOptions">
/// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example:
/// <example>
/// <![CDATA[
/// furtherOptions: "&bgcolor=fff"
/// ]]>
/// </example>
/// </param>
/// <param name="ratioMode">
/// Use a dimension as a ratio
/// </param>
/// <param name="upScale">
/// If the image should be upscaled to requested dimensions
/// </param>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
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);
}
/// <summary>
/// Gets the ImageProcessor Url from the image path.
/// </summary>
/// <param name="imageUrl">
/// The image url.
/// </param>
/// <param name="imageUrlGenerator">
/// The generator that will process all the options and the image URL to return a full image urls with all processing options appended
/// </param>
/// <param name="cropDataSet"></param>
/// <param name="width">
/// The width of the output image.
/// </param>
/// <param name="height">
/// The height of the output image.
/// </param>
/// <param name="cropAlias">
/// The crop alias.
/// </param>
/// <param name="quality">
/// Quality percentage of the output image.
/// </param>
/// <param name="imageCropMode">
/// The image crop mode.
/// </param>
/// <param name="imageCropAnchor">
/// The image crop anchor.
/// </param>
/// <param name="preferFocalPoint">
/// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one
/// </param>
/// <param name="useCropDimensions">
/// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters
/// </param>
/// <param name="cacheBusterValue">
/// Add a serialized date of the last edit of the item to ensure client cache refresh when updated
/// </param>
/// <param name="furtherOptions">
/// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example:
/// <example>
/// <![CDATA[
/// furtherOptions: "&bgcolor=fff"
/// ]]>
/// </example>
/// </param>
/// <param name="ratioMode">
/// Use a dimension as a ratio
/// </param>
/// <param name="upScale">
/// If the image should be upscaled to requested dimensions
/// </param>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
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;
}
}
}

View File

@@ -29,10 +29,7 @@ namespace Umbraco.Web
/// <returns>
/// The ImageProcessor.Web Url.
/// </returns>
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);
/// <summary>
/// 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
/// <returns>
/// The ImageProcessor.Web Url.
/// </returns>
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);
/// <summary>
/// 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<ImageCropperValue>();
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);
/// <summary>
/// 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);
/// <summary>
/// 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)
{

View File

@@ -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();
}
}
}

View File

@@ -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
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
@@ -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;

View File

@@ -36,7 +36,7 @@ namespace Umbraco.Web.PropertyEditors
/// <param name="mediaParentFolder"></param>
/// <param name="userId"></param>
/// <returns></returns>
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);

View File

@@ -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;
/// <summary>
/// The constructor will setup the property editor based on the attribute if one is found
/// </summary>
[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)
{
}
/// <summary>
/// The constructor will setup the property editor based on the attribute if one is found
/// </summary>
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;
}
/// <summary>
/// Create a custom value editor
/// </summary>
/// <returns></returns>
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;
}
/// <inheritdoc />
@@ -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);

View File

@@ -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<DefaultMediaUrlProvider>();
composition.RegisterUnique<IImageUrlGenerator, ImageProcessorImageUrlGenerator>();
composition.RegisterUnique<IContentLastChanceFinder, ContentFinderByConfigured404>();
composition.ContentFinders()

View File

@@ -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<RichTextEditorPastedImages>().FindAndPersistPastedTempImages(html, mediaParentFolder, userId);
=> Current.Factory.GetInstance<RichTextEditorPastedImages>().FindAndPersistPastedTempImages(html, mediaParentFolder, userId, Current.Factory.GetInstance<IImageUrlGenerator>());
}
}

View File

@@ -155,6 +155,7 @@
<Compile Include="Editors\MacrosController.cs" />
<Compile Include="Editors\RelationTypeController.cs" />
<Compile Include="Editors\TinyMceController.cs" />
<Compile Include="ImageCropperTemplateCoreExtensions.cs" />
<Compile Include="IUmbracoContextFactory.cs" />
<Compile Include="Install\ChangesMonitor.cs" />
<Compile Include="Logging\WebProfiler.cs" />
@@ -223,6 +224,7 @@
<Compile Include="Models\ContentEditing\MacroDisplay.cs" />
<Compile Include="Models\ContentEditing\MacroParameterDisplay.cs" />
<Compile Include="Models\ContentEditing\UrlAndAnchors.cs" />
<Compile Include="Models\ImageProcessorImageUrlGenerator.cs" />
<Compile Include="Models\Mapping\CommonMapper.cs" />
<Compile Include="Models\Mapping\MapperContextExtensions.cs" />
<Compile Include="Models\PublishedContent\HybridVariationContextAccessor.cs" />