Merge remote-tracking branch 'origin/v9/dev' into v9/bugfix/Refractor_UmbracoContextAccessor

This commit is contained in:
Elitsa Marinovska
2021-08-17 13:11:45 +02:00
55 changed files with 1429 additions and 1267 deletions

2
.gitignore vendored
View File

@@ -193,6 +193,8 @@ src/Umbraco.Web.UI/Umbraco/telemetrics-id.umb
/src/Umbraco.Web.UI.NetCore/[Uu]mbraco/[Ll]ogs
/src/Umbraco.Web.UI.NetCore/[Uu]mbraco/[Dd]ata/*
/src/Umbraco.Web.UI.NetCore/[Uu]mbraco/[Mm]odels/*
/src/Umbraco.Web.UI.NetCore/appsettings.json
/src/Umbraco.Web.UI.NetCore/appsettings.Development.json
src/Umbraco.Tests.Integration/umbraco/Data/

View File

@@ -24,7 +24,7 @@
"version": {
"type": "parameter",
"datatype": "string",
"defaultValue": "9.0.0-rc001",
"defaultValue": "9.0.0-rc002",
"description": "The version of Umbraco to load using NuGet",
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
},

View File

@@ -57,7 +57,7 @@
"version": {
"type": "parameter",
"datatype": "string",
"defaultValue": "9.0.0-rc001",
"defaultValue": "9.0.0-rc002",
"description": "The version of Umbraco to load using NuGet",
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
},

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<Version>9.0.0</Version>
<AssemblyVersion>9.0.0</AssemblyVersion>
<InformationalVersion>9.0.0-rc001</InformationalVersion>
<InformationalVersion>9.0.0-rc002</InformationalVersion>
<FileVersion>9.0.0</FileVersion>
<LangVersion Condition="'$(LangVersion)' == ''">9.0</LangVersion>
<NeutralLanguage>en-US</NeutralLanguage>

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Core
namespace Umbraco.Cms.Core
{
public static partial class Constants
{
@@ -78,6 +78,12 @@
/// </summary>
public static readonly char[] TildeForwardSlash = new char[] { '~', '/' };
/// <summary>
/// Char array containing ~ / \
/// </summary>
public static readonly char[] TildeForwardSlashBackSlash = new char[] { '~', '/', '\\' };
/// <summary>
/// Char array containing only ?
/// </summary>

View File

@@ -79,26 +79,28 @@ namespace Umbraco.Cms.Core.Diagnostics
private static bool Write(IMarchal marchal, SafeHandle fileHandle, Option options, bool withException = false)
{
var currentProcess = Process.GetCurrentProcess();
var currentProcessHandle = currentProcess.Handle;
var currentProcessId = (uint)currentProcess.Id;
MiniDumpExceptionInformation exp;
exp.ThreadId = GetCurrentThreadId();
exp.ClientPointers = false;
exp.ExceptionPointers = IntPtr.Zero;
if (withException)
using (var currentProcess = Process.GetCurrentProcess())
{
exp.ExceptionPointers = marchal.GetExceptionPointers();
var currentProcessHandle = currentProcess.Handle;
var currentProcessId = (uint)currentProcess.Id;
MiniDumpExceptionInformation exp;
exp.ThreadId = GetCurrentThreadId();
exp.ClientPointers = false;
exp.ExceptionPointers = IntPtr.Zero;
if (withException)
{
exp.ExceptionPointers = marchal.GetExceptionPointers();
}
var bRet = exp.ExceptionPointers == IntPtr.Zero
? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
: MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, ref exp, IntPtr.Zero, IntPtr.Zero);
return bRet;
}
var bRet = exp.ExceptionPointers == IntPtr.Zero
? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
: MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, ref exp, IntPtr.Zero, IntPtr.Zero);
return bRet;
}
public static bool Dump(IMarchal marchal, IHostingEnvironment hostingEnvironment, Option options = Option.WithFullMemory, bool withException = false)

View File

@@ -1,12 +1,28 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Media
{
/// <summary>
/// Exposes a method that generates an image URL based on the specified options.
/// </summary>
public interface IImageUrlGenerator
{
/// <summary>
/// Gets the supported image file types/extensions.
/// </summary>
/// <value>
/// The supported image file types/extensions.
/// </value>
IEnumerable<string> SupportedImageFileTypes { get; }
/// <summary>
/// Gets the image URL based on the specified <paramref name="options" />.
/// </summary>
/// <param name="options">The image URL generation options.</param>
/// <returns>
/// The generated image URL.
/// </returns>
string GetImageUrl(ImageUrlGenerationOptions options);
}
}

View File

@@ -1,8 +0,0 @@
namespace Umbraco.Cms.Core.Models
{
public enum ImageCropRatioMode
{
Width,
Height
}
}

View File

@@ -1,66 +1,68 @@
namespace Umbraco.Cms.Core.Models
namespace Umbraco.Cms.Core.Models
{
/// <summary>
/// These are options that are passed to the IImageUrlGenerator implementation to determine
/// the propery URL that is needed
/// These are options that are passed to the IImageUrlGenerator implementation to determine the URL that is generated.
/// </summary>
public class ImageUrlGenerationOptions
{
public ImageUrlGenerationOptions (string imageUrl)
{
ImageUrl = imageUrl;
}
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; }
public decimal? HeightRatio { get; set; }
public int? Quality { get; set; }
public ImageCropMode? ImageCropMode { get; set; }
public ImageCropAnchor? 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; }
/// <summary>
/// The focal point position, in whatever units the registered IImageUrlGenerator uses,
/// typically a percentage of the total image from 0.0 to 1.0.
/// The focal point position, in whatever units the registered IImageUrlGenerator uses, typically a percentage of the total image from 0.0 to 1.0.
/// </summary>
public class FocalPointPosition
{
public FocalPointPosition (decimal top, decimal left)
public FocalPointPosition(decimal left, decimal top)
{
Left = left;
Top = top;
}
public decimal Left { get; }
public decimal Top { get; }
}
/// <summary>
/// The bounds of the crop within the original image, in whatever units the registered
/// IImageUrlGenerator uses, typically a percentage between 0 and 100.
/// The bounds of the crop within the original image, in whatever units the registered IImageUrlGenerator uses, typically a percentage between 0.0 and 1.0.
/// </summary>
public class CropCoordinates
{
public CropCoordinates (decimal x1, decimal y1, decimal x2, decimal y2)
public CropCoordinates(decimal left, decimal top, decimal right, decimal bottom)
{
X1 = x1;
Y1 = y1;
X2 = x2;
Y2 = y2;
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public decimal X1 { get; }
public decimal Y1 { get; }
public decimal X2 { get; }
public decimal Y2 { get; }
public decimal Left { get; }
public decimal Top { get; }
public decimal Right { get; }
public decimal Bottom { get; }
}
}
}

View File

@@ -84,7 +84,7 @@ namespace Umbraco.Cms.Core.Routing
// and then the comparisons in IsMatch can be way faster - and allocate way less strings
const string propertyAlias = Constants.Conventions.Content.UrlAlias;
var test1 = alias.TrimStart('/') + ",";
var test1 = alias.TrimStart(Constants.CharArrays.ForwardSlash) + ",";
var test2 = ",/" + test1; // test2 is ",/alias,"
test1 = "," + test1; // test1 is ",alias,"

View File

@@ -31,7 +31,7 @@ namespace Umbraco.Cms.Core.Routing
public UmbracoRequestPaths(IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment)
{
var applicationPath = hostingEnvironment.ApplicationVirtualPath;
_appPath = applicationPath.TrimStart('/');
_appPath = applicationPath.TrimStart(Constants.CharArrays.ForwardSlash);
_backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment)
.EnsureStartsWith('/').TrimStart(_appPath.EnsureStartsWith('/')).EnsureStartsWith('/');
@@ -71,7 +71,7 @@ namespace Umbraco.Cms.Core.Routing
/// </remarks>
public bool IsBackOfficeRequest(string absPath)
{
var fullUrlPath = absPath.TrimStart('/');
var fullUrlPath = absPath.TrimStart(Constants.CharArrays.ForwardSlash);
var urlPath = fullUrlPath.TrimStart(_appPath).EnsureStartsWith('/');
// check if this is in the umbraco back office
@@ -108,7 +108,7 @@ namespace Umbraco.Cms.Core.Routing
// Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id}
// so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a
// plugin controller for the front-end.
if (urlPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length >= 3)
if (urlPath.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries).Length >= 3)
{
return false;
}

View File

@@ -5,7 +5,7 @@ using System.Threading.Tasks;
namespace Umbraco.Cms.Core
{
// http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx
// https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-6-asynclock/
//
// notes:
// - this is NOT a reader/writer lock

View File

@@ -145,8 +145,6 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.PropertyValueConverters()
.Remove<SimpleTinyMceValueConverter>();
builder.Services.AddUnique<IImageUrlGenerator, ImageSharpImageUrlGenerator>();
// register *all* checks, except those marked [HideFromTypeFinder] of course
builder.Services.AddUnique<IMarkdownToHtmlConverter, MarkdownToHtmlConverter>();
@@ -183,7 +181,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique<ICronTabParser, NCronTabParser>();
// Add default ImageSharp configuration and service implementations
builder.Services.AddUnique(SixLabors.ImageSharp.Configuration.Default);
builder.Services.AddUnique<IImageDimensionExtractor, ImageDimensionExtractor>();
builder.Services.AddUnique<IImageUrlGenerator, ImageSharpImageUrlGenerator>();
builder.Services.AddUnique<PackageDataInstallation>();

View File

@@ -68,7 +68,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
if (useBackgroundThread)
{
_logger.LogInformation($"Starting async background thread for rebuilding index {indexName}.");
_logger.LogInformation("Starting async background thread for rebuilding index {indexName}.",indexName);
_backgroundTaskQueue.QueueBackgroundWorkItem(
cancellationToken => Task.Run(() => RebuildIndex(indexName, delay.Value, cancellationToken)));

View File

@@ -8,6 +8,17 @@ namespace Umbraco.Cms.Infrastructure.Media
{
internal class ImageDimensionExtractor : IImageDimensionExtractor
{
/// <summary>
/// The ImageSharp configuration.
/// </summary>
private readonly Configuration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="ImageDimensionExtractor" /> class.
/// </summary>
/// <param name="configuration">The ImageSharp configuration.</param>
public ImageDimensionExtractor(Configuration configuration) => _configuration = configuration;
/// <summary>
/// Gets the dimensions of an image.
/// </summary>
@@ -39,7 +50,7 @@ namespace Umbraco.Cms.Infrastructure.Media
stream.Seek(0, SeekOrigin.Begin);
}
using (var image = Image.Load(stream))
using (var image = Image.Load(_configuration, stream))
{
var fileWidth = image.Width;
var fileHeight = image.Height;

View File

@@ -1,68 +1,107 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using SixLabors.ImageSharp;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Media
{
/// <summary>
/// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp.
/// </summary>
/// <seealso cref="Umbraco.Cms.Core.Media.IImageUrlGenerator" />
public class ImageSharpImageUrlGenerator : IImageUrlGenerator
{
public IEnumerable<string> SupportedImageFileTypes => new[] { "jpeg", "jpg", "gif", "bmp", "png" };
/// <inheritdoc />
public IEnumerable<string> SupportedImageFileTypes { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
/// </summary>
/// <param name="configuration">The ImageSharp configuration.</param>
public ImageSharpImageUrlGenerator(Configuration configuration)
: this(configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
/// </summary>
/// <param name="supportedImageFileTypes">The supported image file types/extensions.</param>
/// <remarks>
/// This constructor is only used for testing.
/// </remarks>
internal ImageSharpImageUrlGenerator(IEnumerable<string> supportedImageFileTypes) => SupportedImageFileTypes = supportedImageFileTypes;
/// <inheritdoc/>
public string GetImageUrl(ImageUrlGenerationOptions options)
{
if (options == null) return null;
var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty);
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
if (options == null)
{
imageProcessorUrl.Append("?mode=").Append((options.ImageCropMode ?? ImageCropMode.Crop).ToString().ToLower());
if (options.ImageCropAnchor != null) imageProcessorUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToString().ToLower());
return null;
}
var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format=");
var imageUrl = new StringBuilder(options.ImageUrl);
//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.HasValue && hasFormat == false) imageProcessorUrl.Append("&quality=").Append(options.Quality);
if (options.HeightRatio.HasValue) imageProcessorUrl.Append("&heightratio=").Append(options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture));
if (options.WidthRatio.HasValue) imageProcessorUrl.Append("&widthratio=").Append(options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture));
if (options.Width.HasValue) imageProcessorUrl.Append("&width=").Append(options.Width);
if (options.Height.HasValue) imageProcessorUrl.Append("&height=").Append(options.Height);
if (options.UpScale == false) imageProcessorUrl.Append("&upscale=false");
if (!string.IsNullOrWhiteSpace(options.AnimationProcessMode)) imageProcessorUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode);
if (!string.IsNullOrWhiteSpace(options.FurtherOptions)) imageProcessorUrl.Append(options.FurtherOptions);
bool queryStringHasStarted = false;
void AppendQueryString(string value)
{
imageUrl.Append(queryStringHasStarted ? '&' : '?');
queryStringHasStarted = true;
//If furtherOptions contains a format, we need to put the quality after the format.
if (options.Quality.HasValue && hasFormat) imageProcessorUrl.Append("&quality=").Append(options.Quality);
if (!string.IsNullOrWhiteSpace(options.CacheBusterValue)) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue);
imageUrl.Append(value);
}
void AddQueryString(string key, params IConvertible[] values)
=> AppendQueryString(key + '=' + string.Join(",", values.Select(x => x.ToString(CultureInfo.InvariantCulture))));
return imageProcessorUrl.ToString();
}
if (options.FocalPoint != null)
{
AddQueryString("rxy", options.FocalPoint.Left, options.FocalPoint.Top);
}
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");
}
if (options.Crop != null)
{
AddQueryString("cc", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom);
}
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");
if (options.ImageCropMode.HasValue)
{
AddQueryString("rmode", options.ImageCropMode.Value.ToString().ToLowerInvariant());
}
if (options.ImageCropAnchor.HasValue)
{
AddQueryString("ranchor", options.ImageCropAnchor.Value.ToString().ToLowerInvariant());
}
if (options.Width.HasValue)
{
AddQueryString("width", options.Width.Value);
}
if (options.Height.HasValue)
{
AddQueryString("height", options.Height.Value);
}
if (options.Quality.HasValue)
{
AddQueryString("quality", options.Quality.Value);
}
if (string.IsNullOrWhiteSpace(options.FurtherOptions) == false)
{
AppendQueryString(options.FurtherOptions.TrimStart('?', '&'));
}
if (string.IsNullOrWhiteSpace(options.CacheBusterValue) == false)
{
AddQueryString("rnd", options.CacheBusterValue);
}
return imageUrl.ToString();
}
}
}

View File

@@ -231,8 +231,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
// - 8.15 Final - Current state: {5C424554-A32D-4852-8ED1-A13508187901}
// - 9.0 RC1 - Current state: {5060F3D2-88BE-4D30-8755-CF51F28EAD12}
To<UpdateCmsPropertyGroupIdSeed>("{622E5172-42E1-4662-AD80-9504AF5A4E53}");
To<ExternalLoginTableIndexesFixup>("{10F7BB61-C550-426B-830B-7F954F689CDF}");
To<DictionaryTablesIndexes>("{12DCDE7F-9AB7-4617-804F-AB66BF360980}");
}
}
}

View File

@@ -0,0 +1,136 @@
using System.Linq;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute.Expressions;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
{
public class DictionaryTablesIndexes : MigrationBase
{
private const string IndexedDictionaryColumn = "key";
private const string IndexedLanguageTextColumn = "languageId";
public DictionaryTablesIndexes(IMigrationContext context)
: base(context)
{
}
protected override void Migrate()
{
var indexDictionaryDto = $"IX_{DictionaryDto.TableName}_{IndexedDictionaryColumn}";
var indexLanguageTextDto = $"IX_{LanguageTextDto.TableName}_{IndexedLanguageTextColumn}";
// Delete existing
DeleteIndex<DictionaryDto>(indexDictionaryDto);
// Re-create/Add
AddUniqueConstraint<DictionaryDto>(new[] { IndexedDictionaryColumn }, indexDictionaryDto);
// Delete existing
DeleteIndex<LanguageTextDto>(indexLanguageTextDto);
var langTextcolumns = new[] { IndexedLanguageTextColumn, "UniqueId" };
// Re-create/Add
AddUniqueConstraint<LanguageTextDto>(langTextcolumns, indexLanguageTextDto);
}
private void DeleteIndex<TDto>(string indexName)
{
var tableDef = DefinitionFactory.GetTableDefinition(typeof(TDto), Context.SqlContext.SqlSyntax);
if (IndexExists(indexName))
{
Delete.Index(indexName).OnTable(tableDef.Name).Do();
}
}
private void CreateIndex<TDto>(string indexName)
{
var tableDef = DefinitionFactory.GetTableDefinition(typeof(TDto), Context.SqlContext.SqlSyntax);
// get the definition by name
var index = tableDef.Indexes.First(x => x.Name == indexName);
new ExecuteSqlStatementExpression(Context) { SqlStatement = Context.SqlContext.SqlSyntax.Format(index) }.Execute();
}
private void AddUniqueConstraint<TDto>(string[] columns, string index)
{
var tableDef = DefinitionFactory.GetTableDefinition(typeof(TDto), Context.SqlContext.SqlSyntax);
// Check the existing data to ensure the constraint can be successfully applied.
// This seems to be better than relying on catching an exception as this leads to
// transaction errors: "This SqlTransaction has completed; it is no longer usable".
var columnsDescription = string.Join("], [", columns);
if (ContainsDuplicates<TDto>(columns))
{
var message = $"Could not create unique constraint on [{tableDef.Name}] due to existing " +
$"duplicate records across the column{(columns.Length > 1 ? "s" : string.Empty)}: [{columnsDescription}].";
LogIncompleteMigrationStep(message);
return;
}
CreateIndex<TDto>(index);
}
private bool ContainsDuplicates<TDto>(string[] columns)
{
// Check for duplicates by comparing the total count of all records with the count of records distinct by the
// provided column. If the former is greater than the latter, there's at least one duplicate record.
int recordCount = GetRecordCount<TDto>();
int distinctRecordCount = GetDistinctRecordCount<TDto>(columns);
return recordCount > distinctRecordCount;
}
private int GetRecordCount<TDto>()
{
var countQuery = Database.SqlContext.Sql()
.SelectCount()
.From<TDto>();
return Database.ExecuteScalar<int>(countQuery);
}
private int GetDistinctRecordCount<TDto>(string[] columns)
{
string columnSpecification;
// If using SQL CE, we don't have access to COUNT (DISTINCT *) or CONCAT, so will need to do this by querying all records.
if (DatabaseType.IsSqlCe())
{
columnSpecification = columns.Length == 1
? StringConvertedAndQuotedColumnName(columns[0])
: $"{string.Join(" + ", columns.Select(x => StringConvertedAndQuotedColumnName(x)))}";
var allRecordsQuery = Database.SqlContext.Sql()
.Select(columnSpecification)
.From<TDto>();
var allRecords = Database.Fetch<string>(allRecordsQuery);
return allRecords.Distinct().Count();
}
columnSpecification = columns.Length == 1
? QuoteColumnName(columns[0])
: $"CONCAT({string.Join(",", columns.Select(QuoteColumnName))})";
var distinctCountQuery = Database.SqlContext.Sql()
.Select($"COUNT(DISTINCT({columnSpecification}))")
.From<TDto>();
return Database.ExecuteScalar<int>(distinctCountQuery);
}
private void LogIncompleteMigrationStep(string message) =>
Logger.LogError($"Database migration step failed: {message}");
private string StringConvertedAndQuotedColumnName(string column) => $"CONVERT(nvarchar(1000),{QuoteColumnName(column)})";
private string QuoteColumnName(string column) => $"[{column}]";
}
}

View File

@@ -11,6 +11,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
public class DictionaryDto // public as required to be accessible from Deploy for the RepairDictionaryIdsWorkItem.
{
public const string TableName = Cms.Core.Constants.DatabaseSchema.Tables.DictionaryEntry;
[Column("pk")]
[PrimaryKeyColumn]
public int PrimaryKey { get; set; }
@@ -25,10 +26,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Parent")]
public Guid? Parent { get; set; }
// TODO: This needs to have a unique index.
[Column("key")]
[Length(450)]
[Index(IndexTypes.NonClustered, Name = "IX_cmsDictionary_key")]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_key")]
public string Key { get; set; }
[ResultColumn]

View File

@@ -4,24 +4,26 @@ using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
{
[TableName(Cms.Core.Constants.DatabaseSchema.Tables.DictionaryValue)]
[TableName(TableName)]
[PrimaryKey("pk")]
[ExplicitColumns]
public class LanguageTextDto
{
public const string TableName = Cms.Core.Constants.DatabaseSchema.Tables.DictionaryValue;
[Column("pk")]
[PrimaryKeyColumn]
public int PrimaryKey { get; set; }
[Column("languageId")]
[ForeignKey(typeof(LanguageDto), Column = "id")]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_languageId", ForColumns = "languageId,UniqueId")]
public int LanguageId { get; set; }
[Column("UniqueId")]
[ForeignKey(typeof(DictionaryDto), Column = "id")]
public Guid UniqueId { get; set; }
// TODO: Need a unique constraint on LanguageId, UniqueId, Value
[Column("value")]
[Length(1000)]
public string Value { get; set; }

View File

@@ -906,7 +906,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
return -1;
}
var p = new Process
using (var p = new Process
{
StartInfo =
{
@@ -918,13 +918,15 @@ namespace Umbraco.Cms.Infrastructure.Persistence
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
};
p.Start();
output = p.StandardOutput.ReadToEnd();
error = p.StandardError.ReadToEnd();
p.WaitForExit();
})
{
p.Start();
output = p.StandardOutput.ReadToEnd();
error = p.StandardError.ReadToEnd();
p.WaitForExit();
return p.ExitCode;
return p.ExitCode;
}
}
/// <summary>

View File

@@ -1,4 +1,4 @@
// Copyright (c) Umbraco.
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
@@ -63,13 +63,11 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
: Crops.FirstOrDefault(x => x.Alias.InvariantEquals(alias));
}
public ImageUrlGenerationOptions GetCropBaseOptions(string url, ImageCropperCrop crop, bool defaultCrop, bool preferFocalPoint)
public ImageUrlGenerationOptions GetCropBaseOptions(string url, ImageCropperCrop crop, bool preferFocalPoint)
{
if (preferFocalPoint && HasFocalPoint()
|| crop != null && crop.Coordinates == null && HasFocalPoint()
|| defaultCrop && HasFocalPoint())
if ((preferFocalPoint && HasFocalPoint()) || (crop != null && crop.Coordinates == null && HasFocalPoint()))
{
return new ImageUrlGenerationOptions(url) { FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(FocalPoint.Top, FocalPoint.Left) };
return new ImageUrlGenerationOptions(url) { FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(FocalPoint.Left, FocalPoint.Top) };
}
else if (crop != null && crop.Coordinates != null && preferFocalPoint == false)
{
@@ -77,7 +75,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
}
else
{
return new ImageUrlGenerationOptions(url) { DefaultCrop = true };
return new ImageUrlGenerationOptions(url);
}
}
@@ -92,7 +90,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
if (crop == null && !string.IsNullOrWhiteSpace(alias))
return null;
var options = GetCropBaseOptions(string.Empty, crop, string.IsNullOrWhiteSpace(alias), useFocalPoint);
var options = GetCropBaseOptions(null, crop, useFocalPoint || string.IsNullOrWhiteSpace(alias));
if (crop != null && useCropDimensions)
{
@@ -108,9 +106,9 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
/// <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)
public string GetCropUrl(int width, int height, IImageUrlGenerator imageUrlGenerator, string cacheBusterValue = null)
{
var options = GetCropBaseOptions(string.Empty, null, true, useFocalPoint);
var options = GetCropBaseOptions(null, null, false);
options.Width = width;
options.Height = height;

View File

@@ -72,15 +72,17 @@ namespace Umbraco.Cms.Infrastructure.Sync
GlobalSettings = globalSettings.Value;
_lastPruned = _lastSync = DateTime.UtcNow;
_syncIdle = new ManualResetEvent(true);
// See notes on _localIdentity
LocalIdentity = Environment.MachineName // eg DOMAIN\SERVER
+ "/" + hostingEnvironment.ApplicationId // eg /LM/S3SVC/11/ROOT
+ " [P" + Process.GetCurrentProcess().Id // eg 1234
+ "/D" + AppDomain.CurrentDomain.Id // eg 22
+ "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique
using (var process = Process.GetCurrentProcess())
{
// See notes on _localIdentity
LocalIdentity = Environment.MachineName // eg DOMAIN\SERVER
+ "/" + hostingEnvironment.ApplicationId // eg /LM/S3SVC/11/ROOT
+ " [P" + process.Id // eg 1234
+ "/D" + AppDomain.CurrentDomain.Id // eg 22
+ "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique
}
_initialized = new Lazy<SyncBootState?>(InitializeWithMainDom);
}
public GlobalSettings GlobalSettings { get; }

View File

@@ -524,7 +524,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
parent = GetParentLink(kit.Node, null);
if (parent == null)
{
_logger.LogWarning($"Skip item id={kit.Node.Id}, could not find parent id={kit.Node.ParentContentId}.");
_logger.LogWarning("Skip item id={kit.Node.Id}, could not find parent id={kit.Node.ParentContentId}.", kit.Node.Id, kit.Node.ParentContentId);
return false;
}
@@ -533,21 +533,21 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
// because the data sort operation is by path.
if (parent.Value == null)
{
_logger.LogWarning($"Skip item id={kit.Node.Id}, no Data assigned for linked node with path {kit.Node.Path} and parent id {kit.Node.ParentContentId}. This can indicate data corruption for the Path value for node {kit.Node.Id}. See the Health Check dashboard in Settings to resolve data integrity issues.");
_logger.LogWarning("Skip item id={kit.Node.Id}, no Data assigned for linked node with path {kit.Node.Path} and parent id {kit.Node.ParentContentId}. This can indicate data corruption for the Path value for node {kit.Node.Id}. See the Health Check dashboard in Settings to resolve data integrity issues.", kit.Node.Id, kit.Node.ParentContentId);
return false;
}
// make sure the kit is valid
if (kit.DraftData == null && kit.PublishedData == null)
{
_logger.LogWarning($"Skip item id={kit.Node.Id}, both draft and published data are null.");
_logger.LogWarning("Skip item id={kit.Node.Id}, both draft and published data are null.", kit.Node.Id);
return false;
}
// unknown = bad
if (_contentTypesById.TryGetValue(kit.ContentTypeId, out var link) == false || link.Value == null)
{
_logger.LogWarning($"Skip item id={kit.Node.Id}, could not find content type id={kit.ContentTypeId}.");
_logger.LogWarning("Skip item id={kit.Node.Id}, could not find content type id={kit.ContentTypeId}.", kit.Node.Id, kit.ContentTypeId);
return false;
}
@@ -723,7 +723,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
previousNode = null; // there is no previous sibling
}
_logger.LogDebug($"Set {thisNode.Id} with parent {thisNode.ParentContentId}");
_logger.LogDebug("Set {thisNode.Id} with parent {thisNode.ParentContentId}", thisNode.Id, thisNode.ParentContentId);
SetValueLocked(_contentNodes, thisNode.Id, thisNode);
// if we are initializing from the database source ensure the local db is updated
@@ -780,7 +780,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
ok = false;
continue; // skip that one
}
_logger.LogDebug($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}");
_logger.LogDebug("Set {kit.Node.Id} with parent {kit.Node.ParentContentId}", kit.Node.Id, kit.Node.ParentContentId);
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
if (_localDb != null) RegisterChange(kit.Node.Id, kit);

View File

@@ -463,7 +463,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
// Update: We will still return false here even though the above mentioned race condition has been fixed since we now
// lock the entire operation of creating/populating the cache file with the same lock as releasing/closing the cache file
_logger.LogInformation($"Tried to load {entityType} from the local cache file but it was empty.");
_logger.LogInformation("Tried to load {entityType} from the local cache file but it was empty.", entityType);
return false;
}

View File

@@ -136,7 +136,7 @@ namespace Umbraco.Cms.Tests.Integration.Implementations
{
var globalSettings = new GlobalSettings();
IOptionsMonitor<GlobalSettings> mockedOptionsMonitorOfGlobalSettings = Mock.Of<IOptionsMonitor<GlobalSettings>>(x => x.CurrentValue == globalSettings);
_backOfficeInfo = new AspNetCoreBackOfficeInfo(mockedOptionsMonitorOfGlobalSettings);
_backOfficeInfo = new AspNetCoreBackOfficeInfo(mockedOptionsMonitorOfGlobalSettings, GetHostingEnvironment());
}
return _backOfficeInfo;

View File

@@ -1,6 +1,7 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -766,7 +767,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Packaging
new DictionaryItem("Parent")
{
// This matches what is in the package.xml file
Key = new System.Guid("28f2e02a-8c66-4fcd-85e3-8524d551c0d3"),
Key = new Guid("28f2e02a-8c66-4fcd-85e3-8524d551c0d3"),
Translations = new List<IDictionaryTranslation>
{
new DictionaryTranslation(englishLanguage, expectedEnglishParentValue),
@@ -782,6 +783,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Packaging
LocalizationService.Save(
new DictionaryItem("Parent")
{
// This matches what is in the package.xml file
Key = new Guid("28f2e02a-8c66-4fcd-85e3-8524d551c0d3"),
Translations = new List<IDictionaryTranslation>
{
new DictionaryTranslation(englishLanguage, expectedEnglishParentValue),

View File

@@ -12,40 +12,40 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
{
private const string MediaPath = "/media/1005/img_0671.jpg";
private static readonly ImageUrlGenerationOptions.CropCoordinates s_crop = new ImageUrlGenerationOptions.CropCoordinates(0.58729977382575338m, 0.055768992440203169m, 0m, 0.32457553600198386m);
private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus1 = new ImageUrlGenerationOptions.FocalPointPosition(0.80827067669172936m, 0.96m);
private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus2 = new ImageUrlGenerationOptions.FocalPointPosition(0.41m, 0.4275m);
private static readonly ImageSharpImageUrlGenerator s_generator = new ImageSharpImageUrlGenerator();
private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus1 = new ImageUrlGenerationOptions.FocalPointPosition(0.96m, 0.80827067669172936m);
private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus2 = new ImageUrlGenerationOptions.FocalPointPosition(0.4275m, 0.41m);
private static readonly ImageSharpImageUrlGenerator s_generator = new ImageSharpImageUrlGenerator(new string[0]);
[Test]
public void GetCropUrl_CropAliasTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, Height = 100 });
Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString);
Assert.AreEqual(MediaPath + "?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString);
}
[Test]
public void GetCropUrl_WidthHeightTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 200, Height = 300 });
Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300", urlString);
Assert.AreEqual(MediaPath + "?rxy=0.96,0.80827067669172936&width=200&height=300", urlString);
}
[Test]
public void GetCropUrl_FocalPointTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 100, Height = 100 });
Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=100&height=100", urlString);
Assert.AreEqual(MediaPath + "?rxy=0.96,0.80827067669172936&width=100&height=100", urlString);
}
[Test]
public void GetCropUrlFurtherOptionsTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_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);
Assert.AreEqual(MediaPath + "?rxy=0.96,0.80827067669172936&width=200&height=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString);
}
/// <summary>
/// Test that if a crop alias has been specified that doesn't exist the method returns null
/// Test that if options is null, the generated image URL is also null.
/// </summary>
[Test]
public void GetCropUrlNullTest()
@@ -55,13 +55,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
}
/// <summary>
/// Test that if a crop alias has been specified that doesn't exist the method returns null
/// Test that if the image URL is null, the generated image URL is empty.
/// </summary>
[Test]
public void GetCropUrlEmptyTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null));
Assert.AreEqual("?mode=crop", urlString);
Assert.AreEqual(string.Empty, urlString);
}
/// <summary>
@@ -71,37 +71,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
public void GetBaseCropUrlFromModelTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null) { Crop = s_crop, Width = 100, Height = 100 });
Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString);
}
/// <summary>
/// Test the height ratio mode with predefined crop dimensions
/// </summary>
[Test]
public void GetCropUrl_CropAliasHeightRatioModeTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, HeightRatio = 1 });
Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString);
}
/// <summary>
/// Test the height ratio mode with manual width/height dimensions
/// </summary>
[Test]
public void GetCropUrl_WidthHeightRatioModeTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 300, HeightRatio = 0.5m });
Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&heightratio=0.5&width=300", urlString);
}
/// <summary>
/// Test the height ratio mode with width/height dimensions
/// </summary>
[Test]
public void GetCropUrl_HeightWidthRatioModeTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Height = 150, WidthRatio = 2 });
Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&widthratio=2&height=150", urlString);
Assert.AreEqual("?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString);
}
/// <summary>
@@ -116,11 +86,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
var urlStringMax = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Max, Width = 300, Height = 150 });
var urlStringStretch = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = 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);
Assert.AreEqual(MediaPath + "?rmode=min&width=300&height=150", urlStringMin);
Assert.AreEqual(MediaPath + "?rmode=boxpad&width=300&height=150", urlStringBoxPad);
Assert.AreEqual(MediaPath + "?rmode=pad&width=300&height=150", urlStringPad);
Assert.AreEqual(MediaPath + "?rmode=max&width=300&height=150", urlStringMax);
Assert.AreEqual(MediaPath + "?rmode=stretch&width=300&height=150", urlStringStretch);
}
/// <summary>
@@ -130,7 +100,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
public void GetCropUrl_UploadTypeTest()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Crop, ImageCropAnchor = ImageCropAnchor.Center, Width = 100, Height = 270 });
Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString);
Assert.AreEqual(MediaPath + "?rmode=crop&ranchor=center&width=100&height=270", urlString);
}
/// <summary>
@@ -139,28 +109,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
[Test]
public void GetCropUrl_PreferFocalPointCenter()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 300, Height = 150 });
Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString);
}
/// <summary>
/// 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
/// </summary>
[Test]
public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth()
{
var urlString = s_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);
}
/// <summary>
/// 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
/// </summary>
[Test]
public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus2, Width = 200, HeightRatio = 0.5962962962962962962962962963m });
Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString);
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Width = 300, Height = 150 });
Assert.AreEqual(MediaPath + "?width=300&height=150", urlString);
}
/// <summary>
@@ -170,17 +120,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus2, Width = 270, Height = 161 });
Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString);
}
/// <summary>
/// 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
/// </summary>
[Test]
public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight()
{
var urlString = s_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);
Assert.AreEqual(MediaPath + "?rxy=0.4275,0.41&width=270&height=161", urlString);
}
/// <summary>
@@ -189,8 +129,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
[Test]
public void GetCropUrl_WidthOnlyParameter()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200 });
Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=200", urlString);
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Width = 200 });
Assert.AreEqual(MediaPath + "?width=200", urlString);
}
/// <summary>
@@ -199,8 +139,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
[Test]
public void GetCropUrl_HeightOnlyParameter()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200 });
Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&height=200", urlString);
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Height = 200 });
Assert.AreEqual(MediaPath + "?height=200", urlString);
}
/// <summary>
@@ -210,7 +150,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media
public void GetCropUrl_BackgroundColorParameter()
{
var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Pad, Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" });
Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString);
Assert.AreEqual(MediaPath + "?rmode=pad&width=400&height=400&bgcolor=fff", urlString);
}
}
}

View File

@@ -0,0 +1,114 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Extensions
{
[TestFixture]
public class ImageCropperTemplateCoreExtensionsTests
{
[Test]
public void GetCropUrl_WithCropSpecifiedButNotFound_ReturnsNull()
{
var imageUrl = "/test.jpg";
Mock<IImageUrlGenerator> imageUrlGenerator = CreateMockImageUrlGenerator();
var result = imageUrl.GetCropUrl(
imageUrlGenerator.Object,
new ImageCropperValue { },
imageCropMode: ImageCropMode.Crop,
cropAlias: "Missing");
Assert.IsNull(result);
}
[Test]
public void GetCropUrl_WithCropSpecifiedAndUsingCropDimensions_CallsImageGeneratorWithCorrectParameters()
{
var imageUrl = "/test.jpg";
Mock<IImageUrlGenerator> imageUrlGenerator = CreateMockImageUrlGenerator();
var result = imageUrl.GetCropUrl(
imageUrlGenerator.Object,
CreateImageCropperValueWithCrops(),
imageCropMode: ImageCropMode.Crop,
cropAlias: "TestCrop",
useCropDimensions: true);
imageUrlGenerator
.Verify(x => x.GetImageUrl(
It.Is<ImageUrlGenerationOptions>(y => y.Width == 100 &&
y.Height == 200)));
}
[Test]
public void GetCropUrl_WithCropSpecifiedAndWidthAndHeightProvided_CallsImageGeneratorWithCorrectParameters()
{
var imageUrl = "/test.jpg";
Mock<IImageUrlGenerator> imageUrlGenerator = CreateMockImageUrlGenerator();
var result = imageUrl.GetCropUrl(
imageUrlGenerator.Object,
CreateImageCropperValueWithCrops(),
imageCropMode: ImageCropMode.Crop,
cropAlias: "TestCrop",
width: 50,
height: 80);
imageUrlGenerator
.Verify(x => x.GetImageUrl(
It.Is<ImageUrlGenerationOptions>(y => y.Width == 50 &&
y.Height == 80)));
}
[Test]
public void GetCropUrl_WithCropSpecifiedAndWidthOnlyProvided_CallsImageGeneratorWithCorrectParameters()
{
var imageUrl = "/test.jpg";
Mock<IImageUrlGenerator> imageUrlGenerator = CreateMockImageUrlGenerator();
var result = imageUrl.GetCropUrl(
imageUrlGenerator.Object,
CreateImageCropperValueWithCrops(),
imageCropMode: ImageCropMode.Crop,
cropAlias: "TestCrop",
width: 50);
imageUrlGenerator
.Verify(x => x.GetImageUrl(
It.Is<ImageUrlGenerationOptions>(y => y.Width == 50 &&
y.Height == 100)));
}
[Test]
public void GetCropUrl_WithCropSpecifiedAndHeightOnlyProvided_CallsImageGeneratorWithCorrectParameters()
{
var imageUrl = "/test.jpg";
Mock<IImageUrlGenerator> imageUrlGenerator = CreateMockImageUrlGenerator();
var result = imageUrl.GetCropUrl(
imageUrlGenerator.Object,
CreateImageCropperValueWithCrops(),
imageCropMode: ImageCropMode.Crop,
cropAlias: "TestCrop",
height: 50);
imageUrlGenerator
.Verify(x => x.GetImageUrl(
It.Is<ImageUrlGenerationOptions>(y => y.Width == 25 &&
y.Height == 50)));
}
private static Mock<IImageUrlGenerator> CreateMockImageUrlGenerator() => new Mock<IImageUrlGenerator>();
private static ImageCropperValue CreateImageCropperValueWithCrops() => new ImageCropperValue
{
Crops = new List<ImageCropperValue.ImageCropperCrop>
{
new ImageCropperValue.ImageCropperCrop { Alias = "TestCrop", Width = 100, Height = 200 },
}
};
}
}

View File

@@ -1,8 +1,10 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -130,21 +132,21 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
public void GetCropUrl_WidthHeightTest()
{
var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 200, height: 300);
Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=200&h=300", urlString);
Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.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 + "?f=0.80827067669172936x0.96&w=100&h=100", urlString);
Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.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 + "?f=0.80827067669172936x0.96&w=200&h=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString);
var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 200, height: 300, furtherOptions: "filter=comic&roundedcorners=radius-26|bgcolor-fff");
Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=200&h=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString);
}
/// <summary>
@@ -174,8 +176,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
[Test]
public void GetCropUrl_CropAliasHeightRatioModeTest()
{
var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode: ImageCropRatioMode.Height);
Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&hr=1&w=100", urlString);
var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true);
Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString);
}
/// <summary>
@@ -184,8 +186,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
[Test]
public void GetCropUrl_WidthHeightRatioModeTest()
{
var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Height);
Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&hr=0.5&w=300", urlString);
var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150);
Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=300&h=150", urlString);
}
/// <summary>
@@ -194,8 +196,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
[Test]
public void GetCropUrl_HeightWidthRatioModeTest()
{
var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width);
Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&wr=2&h=150", urlString);
var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150);
Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=300&h=150", urlString);
}
/// <summary>
@@ -236,7 +238,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
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 + "?m=defaultcrop&w=300&h=150", urlString);
Assert.AreEqual(MediaPath + "?w=300&h=150", urlString);
}
/// <summary>
@@ -248,7 +250,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
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 + "?m=defaultcrop&hr=0.5962962962962962962962962963&w=200", urlString);
Assert.AreEqual(MediaPath + "?w=200&h=119", urlString);
}
/// <summary>
@@ -260,7 +262,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
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 + "?f=0.41x0.4275&hr=0.5962962962962962962962962963&w=200", urlString);
Assert.AreEqual(MediaPath + "?f=0.41,0.4275&w=200&h=119", urlString);
}
/// <summary>
@@ -272,7 +274,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
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 + "?f=0.41x0.4275&w=270&h=161", urlString);
Assert.AreEqual(MediaPath + "?f=0.41,0.4275&w=270&h=161", urlString);
}
/// <summary>
@@ -284,7 +286,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
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 + "?m=defaultcrop&wr=1.6770186335403726708074534161&h=200", urlString);
Assert.AreEqual(MediaPath + "?w=335&h=200", urlString);
}
/// <summary>
@@ -296,7 +298,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
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 + "?m=defaultcrop&w=200", urlString);
Assert.AreEqual(MediaPath + "?w=200", urlString);
}
/// <summary>
@@ -308,7 +310,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
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 + "?m=defaultcrop&h=200", urlString);
Assert.AreEqual(MediaPath + "?h=200", urlString);
}
/// <summary>
@@ -319,98 +321,88 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
{
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");
var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, furtherOptions: "bgcolor=fff");
Assert.AreEqual(MediaPath + "?m=pad&w=400&h=400&bgcolor=fff", urlString);
}
internal class TestImageUrlGenerator : IImageUrlGenerator
{
public IEnumerable<string> SupportedImageFileTypes => new[] { "jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif" };
public IEnumerable<string> SupportedImageFileTypes => new[]
{
"jpeg",
"jpg",
"gif",
"bmp",
"png",
"tiff",
"tif"
};
public string GetImageUrl(ImageUrlGenerationOptions options)
{
var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty);
if (options == null)
{
return null;
}
var imageUrl = new StringBuilder(options.ImageUrl);
bool queryStringHasStarted = false;
void AppendQueryString(string value)
{
imageUrl.Append(queryStringHasStarted ? '&' : '?');
queryStringHasStarted = true;
imageUrl.Append(value);
}
void AddQueryString(string key, params IConvertible[] values)
=> AppendQueryString(key + '=' + string.Join(",", values.Select(x => x.ToString(CultureInfo.InvariantCulture))));
if (options.FocalPoint != null)
{
imageProcessorUrl.Append("?f=");
imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture));
imageProcessorUrl.Append("x");
imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture));
AddQueryString("f", options.FocalPoint.Top, options.FocalPoint.Left);
}
else if (options.Crop != null)
{
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));
}
else if (options.DefaultCrop)
{
imageProcessorUrl.Append("?m=defaultcrop");
}
else
{
imageProcessorUrl.Append("?m=" + options.ImageCropMode.ToString().ToLower());
if (options.ImageCropAnchor != null)
{
imageProcessorUrl.Append("&a=" + options.ImageCropAnchor.ToString().ToLower());
}
AddQueryString("c", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom);
}
var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&f=");
if (options.Quality != null && hasFormat == false)
if (options.ImageCropMode.HasValue)
{
imageProcessorUrl.Append("&q=" + options.Quality);
AddQueryString("m", options.ImageCropMode.Value.ToString().ToLowerInvariant());
}
if (options.HeightRatio != null)
if (options.ImageCropAnchor.HasValue)
{
imageProcessorUrl.Append("&hr=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture));
}
if (options.WidthRatio != null)
{
imageProcessorUrl.Append("&wr=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture));
AddQueryString("a", options.ImageCropAnchor.Value.ToString().ToLowerInvariant());
}
if (options.Width != null)
{
imageProcessorUrl.Append("&w=" + options.Width);
AddQueryString("w", options.Width.Value);
}
if (options.Height != null)
{
imageProcessorUrl.Append("&h=" + options.Height);
AddQueryString("h", options.Height.Value);
}
if (options.UpScale == false)
if (options.Quality.HasValue)
{
imageProcessorUrl.Append("&u=no");
}
if (options.AnimationProcessMode != null)
{
imageProcessorUrl.Append("&apm=" + options.AnimationProcessMode);
AddQueryString("q", options.Quality.Value);
}
if (options.FurtherOptions != null)
{
imageProcessorUrl.Append(options.FurtherOptions);
}
if (options.Quality != null && hasFormat)
{
imageProcessorUrl.Append("&q=" + options.Quality);
AppendQueryString(options.FurtherOptions.TrimStart('?', '&'));
}
if (options.CacheBusterValue != null)
{
imageProcessorUrl.Append("&r=").Append(options.CacheBusterValue);
AddQueryString("r", options.CacheBusterValue);
}
return imageProcessorUrl.ToString();
return imageUrl.ToString();
}
}
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using NUnit.Framework;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Web;
using SixLabors.ImageSharp.Web.Commands;
using SixLabors.ImageSharp.Web.Commands.Converters;
using Umbraco.Cms.Web.Common.ImageProcessors;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.ImageProcessors
{
[TestFixture]
public class CropWebProcessorTests
{
[Test]
public void CropWebProcessor_CropsImage()
{
var converters = new List<ICommandConverter>
{
CreateArrayConverterOfFloat(),
CreateSimpleCommandConverterOfFloat(),
};
var parser = new CommandParser(converters);
CultureInfo culture = CultureInfo.InvariantCulture;
var commands = new Dictionary<string, string>
{
{ CropWebProcessor.Coordinates, "0.1,0.2,0.1,0.4" }, // left, top, right, bottom
};
using var image = new Image<Rgba32>(50, 80);
using FormattedImage formatted = CreateFormattedImage(image, PngFormat.Instance);
new CropWebProcessor().Process(formatted, null, commands, parser, culture);
Assert.AreEqual(40, image.Width); // Cropped 5 pixels from each side.
Assert.AreEqual(32, image.Height); // Cropped 16 pixels from the top and 32 from the bottom.
}
private static ICommandConverter CreateArrayConverterOfFloat()
{
// ImageSharp.Web's ArrayConverter is internal, so we need to use reflection to instantiate.
var type = Type.GetType("SixLabors.ImageSharp.Web.Commands.Converters.ArrayConverter`1, SixLabors.ImageSharp.Web");
Type[] typeArgs = { typeof(float) };
Type genericType = type.MakeGenericType(typeArgs);
return (ICommandConverter)Activator.CreateInstance(genericType);
}
private static ICommandConverter CreateSimpleCommandConverterOfFloat()
{
// ImageSharp.Web's SimpleCommandConverter is internal, so we need to use reflection to instantiate.
var type = Type.GetType("SixLabors.ImageSharp.Web.Commands.Converters.SimpleCommandConverter`1, SixLabors.ImageSharp.Web");
Type[] typeArgs = { typeof(float) };
Type genericType = type.MakeGenericType(typeArgs);
return (ICommandConverter)Activator.CreateInstance(genericType);
}
private FormattedImage CreateFormattedImage(Image<Rgba32> image, PngFormat format)
{
// Again, the constructor of FormattedImage useful for tests is internal, so we need to use reflection.
Type type = typeof(FormattedImage);
var instance = type.Assembly.CreateInstance(
type.FullName,
false,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new object[] { image, format },
null,
null);
return (FormattedImage)instance;
}
}
}

View File

@@ -365,7 +365,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
},
{
"imageUrlGeneratorApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<ImageUrlGeneratorController>(
controller => controller.GetCropUrl(null, null, null, null, null))
controller => controller.GetCropUrl(null, null, null, null))
},
{
"elementTypeApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<ElementTypeController>(

View File

@@ -1,4 +1,4 @@
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Web.Common.Attributes;
using Constants = Umbraco.Cms.Core.Constants;
@@ -21,20 +21,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
private readonly IImageUrlGenerator _imageUrlGenerator;
public ImageUrlGeneratorController(IImageUrlGenerator imageUrlGenerator)
{
_imageUrlGenerator = imageUrlGenerator;
}
public ImageUrlGeneratorController(IImageUrlGenerator imageUrlGenerator) => _imageUrlGenerator = imageUrlGenerator;
public string GetCropUrl(string mediaPath, int? width = null, int? height = null, ImageCropMode? imageCropMode = null, string animationProcessMode = null)
public string GetCropUrl(string mediaPath, int? width = null, int? height = null, ImageCropMode? imageCropMode = null) => _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(mediaPath)
{
return _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(mediaPath)
{
Width = width,
Height = height,
ImageCropMode = imageCropMode,
AnimationProcessMode = animationProcessMode
});
}
Width = width,
Height = height,
ImageCropMode = imageCropMode
});
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.IO;
@@ -76,9 +76,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null;
var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath)
{
UpScale = false,
Width = width,
AnimationProcessMode = "first",
ImageCropMode = ImageCropMode.Max,
CacheBusterValue = rnd
});
@@ -94,9 +92,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// <param name="height"></param>
/// <param name="focalPointLeft"></param>
/// <param name="focalPointTop"></param>
/// <param name="animationProcessMode"></param>
/// <param name="mode"></param>
/// <param name="upscale"></param>
/// <returns></returns>
/// <remarks>
/// If there is no media, image property or image file is found then this will return not found.
@@ -106,9 +102,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
int? height = null,
decimal? focalPointLeft = null,
decimal? focalPointTop = null,
string animationProcessMode = "first",
ImageCropMode mode = ImageCropMode.Max,
bool upscale = false,
string cacheBusterValue = "",
decimal? cropX1 = null,
decimal? cropX2 = null,
@@ -116,45 +110,24 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
decimal? cropY2 = null
)
{
var options = new ImageUrlGenerationOptions(imagePath)
{
AnimationProcessMode = animationProcessMode,
CacheBusterValue = cacheBusterValue,
Width = width,
Height = height,
ImageCropMode = mode,
UpScale = upscale,
Width = width,
Crop = (cropX1.HasValue && cropX2.HasValue && cropY1.HasValue && cropY2.HasValue) ? new ImageUrlGenerationOptions.CropCoordinates(cropX1.Value, cropY1.Value, cropX2.Value, cropY2.Value) : null,
FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(focalPointTop.GetValueOrDefault(0.5m), focalPointLeft.GetValueOrDefault(0.5m)),
CacheBusterValue = cacheBusterValue
};
if (focalPointLeft.HasValue && focalPointTop.HasValue)
{
options.FocalPoint =
new ImageUrlGenerationOptions.FocalPointPosition(focalPointTop.Value, focalPointLeft.Value);
options.FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(focalPointLeft.Value, focalPointTop.Value);
}
else if (cropX1.HasValue && cropX2.HasValue && cropY1.HasValue && cropY2.HasValue)
{
options.Crop = new ImageUrlGenerationOptions.CropCoordinates(cropX1.Value, cropY1.Value, cropX2.Value, cropY2.Value);
}
return _imageUrlGenerator.GetImageUrl(options);
}
public class FocalPointPositionModel
{
public decimal Left { get; set; }
public decimal Top { get; set; }
}
/// <summary>
/// The bounds of the crop within the original image, in whatever units the registered
/// IImageUrlGenerator uses, typically a percentage between 0 and 100.
/// </summary>
public class CropCoordinatesModel
{
public decimal X1 { get; set; }
public decimal Y1 { get; set; }
public decimal X2 { get; set;}
public decimal Y2 { get; set;}
}
}
}

View File

@@ -1,17 +1,38 @@
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Routing;
using static Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.Common.AspNetCore
{
public class AspNetCoreBackOfficeInfo : IBackOfficeInfo
{
public AspNetCoreBackOfficeInfo(IOptionsMonitor<GlobalSettings> globalSettings)
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private string _getAbsoluteUrl;
public AspNetCoreBackOfficeInfo(IOptionsMonitor<GlobalSettings> globalSettings, IHostingEnvironment hostingEnviroment)
{
GetAbsoluteUrl = globalSettings.CurrentValue.UmbracoPath;
_globalSettings = globalSettings;
_hostingEnvironment = hostingEnviroment;
}
public string GetAbsoluteUrl { get; } // TODO make absolute
public string GetAbsoluteUrl
{
get
{
if (_getAbsoluteUrl is null)
{
if(_hostingEnvironment.ApplicationMainUrl is null)
{
return "";
}
_getAbsoluteUrl = WebPath.Combine(_hostingEnvironment.ApplicationMainUrl.ToString(), _globalSettings.CurrentValue.UmbracoPath.TrimStart(CharArrays.TildeForwardSlash));
}
return _getAbsoluteUrl;
}
}
}
}

View File

@@ -141,7 +141,7 @@ namespace Umbraco.Cms.Web.Common.AspNetCore
throw new ArgumentException("The path appears to already be fully qualified. Please remove the call to MapPath");
}
return Path.Combine(root, newPath.TrimStart('~', '/', '\\'));
return Path.Combine(root, newPath.TrimStart(Core.Constants.CharArrays.TildeForwardSlashBackSlash));
}
/// <inheritdoc/>

View File

@@ -0,0 +1,30 @@
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Web.Middleware;
namespace Umbraco.Cms.Web.Common.DependencyInjection
{
/// <summary>
/// Configures the ImageSharp middleware options to use the registered configuration.
/// </summary>
/// <seealso cref="Microsoft.Extensions.Options.IConfigureOptions&lt;SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddlewareOptions&gt;" />
public sealed class ImageSharpConfigurationOptions : IConfigureOptions<ImageSharpMiddlewareOptions>
{
/// <summary>
/// The ImageSharp configuration.
/// </summary>
private readonly Configuration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="ImageSharpConfigurationOptions" /> class.
/// </summary>
/// <param name="configuration">The ImageSharp configuration.</param>
public ImageSharpConfigurationOptions(Configuration configuration) => _configuration = configuration;
/// <summary>
/// Invoked to configure a <typeparamref name="TOptions" /> instance.
/// </summary>
/// <param name="options">The options instance to configure.</param>
public void Configure(ImageSharpMiddlewareOptions options) => options.Configuration = _configuration;
}
}

View File

@@ -2,14 +2,16 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SixLabors.ImageSharp.Memory;
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp.Web.Caching;
using SixLabors.ImageSharp.Web.Commands;
using SixLabors.ImageSharp.Web.DependencyInjection;
using SixLabors.ImageSharp.Web.Middleware;
using SixLabors.ImageSharp.Web.Processors;
using SixLabors.ImageSharp.Web.Providers;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Cms.Web.Common.ImageProcessors;
namespace Umbraco.Extensions
{
@@ -20,56 +22,44 @@ namespace Umbraco.Extensions
/// </summary>
public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder builder)
{
IConfiguration configuration = builder.Config;
IServiceCollection services = builder.Services;
ImagingSettings imagingSettings = configuration.GetSection(Cms.Core.Constants.Configuration.ConfigImaging)
ImagingSettings imagingSettings = builder.Config.GetSection(Cms.Core.Constants.Configuration.ConfigImaging)
.Get<ImagingSettings>() ?? new ImagingSettings();
services.AddImageSharp(options =>
builder.Services.AddImageSharp(options =>
{
options.Configuration = SixLabors.ImageSharp.Configuration.Default;
// The configuration is set using ImageSharpConfigurationOptions
options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge;
options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge;
options.CachedNameLength = imagingSettings.Cache.CachedNameLength;
// Use configurable maximum width and height (overwrite ImageSharps default)
options.OnParseCommandsAsync = context =>
{
RemoveIntParamenterIfValueGreatherThen(context.Commands, ResizeWebProcessor.Width, imagingSettings.Resize.MaxWidth);
RemoveIntParamenterIfValueGreatherThen(context.Commands, ResizeWebProcessor.Height, imagingSettings.Resize.MaxHeight);
if (context.Commands.Count == 0)
{
return Task.CompletedTask;
}
uint width = context.Parser.ParseValue<uint>(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture);
uint height = context.Parser.ParseValue<uint>(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture);
if (width > imagingSettings.Resize.MaxWidth || height > imagingSettings.Resize.MaxHeight)
{
context.Commands.Remove(ResizeWebProcessor.Width);
context.Commands.Remove(ResizeWebProcessor.Height);
}
return Task.CompletedTask;
};
options.OnBeforeSaveAsync = _ => Task.CompletedTask;
options.OnProcessedAsync = _ => Task.CompletedTask;
options.OnPrepareResponseAsync = _ => Task.CompletedTask;
})
.SetRequestParser<QueryCollectionRequestParser>()
.Configure<PhysicalFileSystemCacheOptions>(options =>
{
options.CacheFolder = imagingSettings.Cache.CacheFolder;
})
.SetCache<PhysicalFileSystemCache>()
.SetCacheHash<CacheHash>()
.AddProvider<PhysicalFileSystemProvider>()
.AddProcessor<ResizeWebProcessor>()
.AddProcessor<FormatWebProcessor>()
.AddProcessor<BackgroundColorWebProcessor>();
.Configure<PhysicalFileSystemCacheOptions>(options => options.CacheFolder = imagingSettings.Cache.CacheFolder)
// We need to add CropWebProcessor before ResizeWebProcessor (until https://github.com/SixLabors/ImageSharp.Web/issues/182 is fixed)
.RemoveProcessor<ResizeWebProcessor>()
.AddProcessor<CropWebProcessor>()
.AddProcessor<ResizeWebProcessor>();
return services;
}
builder.Services.AddTransient<IConfigureOptions<ImageSharpMiddlewareOptions>, ImageSharpConfigurationOptions>();
private static void RemoveIntParamenterIfValueGreatherThen(IDictionary<string, string> commands, string parameter, int maxValue)
{
if (commands.TryGetValue(parameter, out var command))
{
if (int.TryParse(command, out var i))
{
if (i > maxValue)
{
commands.Remove(parameter);
}
}
}
return builder.Services;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
@@ -11,25 +11,17 @@ namespace Umbraco.Extensions
{
public static class FriendlyImageCropperTemplateExtensions
{
private static IImageUrlGenerator ImageUrlGenerator { get; } =
StaticServiceProvider.Instance.GetRequiredService<IImageUrlGenerator>();
private static IImageUrlGenerator ImageUrlGenerator { get; } = StaticServiceProvider.Instance.GetRequiredService<IImageUrlGenerator>();
private static IPublishedValueFallback PublishedValueFallback { get; } =
StaticServiceProvider.Instance.GetRequiredService<IPublishedValueFallback>();
private static IPublishedUrlProvider PublishedUrlProvider { get; } =
StaticServiceProvider.Instance.GetRequiredService<IPublishedUrlProvider>();
private static IPublishedValueFallback PublishedValueFallback { get; } = StaticServiceProvider.Instance.GetRequiredService<IPublishedValueFallback>();
private static IPublishedUrlProvider PublishedUrlProvider { get; } = StaticServiceProvider.Instance.GetRequiredService<IPublishedUrlProvider>();
/// <summary>
/// Gets the underlying image processing service URL by the crop alias (from the "umbracoFile" property alias) on the IPublishedContent item
/// Gets the underlying image processing service 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>
/// <param name="mediaItem">The IPublishedContent item.</param>
/// <param name="cropAlias">The crop alias e.g. thumbnail.</param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
@@ -57,17 +49,11 @@ namespace Umbraco.Extensions
=> ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, imageCropperValue, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider);
/// <summary>
/// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper Json data on the IPublishedContent item.
/// Gets the underlying image processing service 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>
/// <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 URL of the cropped image.
/// </returns>
@@ -83,53 +69,21 @@ namespace Umbraco.Extensions
/// <summary>
/// Gets the underlying image processing service 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 the underlying image processing service 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>
/// <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 the underlying image processing service supports. For example:
/// <example><![CDATA[
/// furtherOptions: "bgcolor=fff"
/// ]]></example></param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
@@ -145,9 +99,7 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true)
string furtherOptions = null)
=> mediaItem.GetCropUrl(
ImageUrlGenerator,
PublishedValueFallback,
@@ -162,63 +114,29 @@ namespace Umbraco.Extensions
preferFocalPoint,
useCropDimensions,
cacheBuster,
furtherOptions,
ratioMode,
upScale
furtherOptions
);
/// <summary>
/// Gets the underlying image processing service 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 the underlying image processing service 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>
/// <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 the underlying image processing service supports. For example:
/// <example><![CDATA[
/// furtherOptions: "bgcolor=fff"
/// ]]></example></param>
/// <returns>
/// The the URL of the cropped image.
/// The URL of the cropped image.
/// </returns>
public static string GetCropUrl(
this string imageUrl,
@@ -232,9 +150,7 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
string cacheBusterValue = null,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true)
string furtherOptions = null)
=> imageUrl.GetCropUrl(
ImageUrlGenerator,
width,
@@ -247,61 +163,29 @@ namespace Umbraco.Extensions
preferFocalPoint,
useCropDimensions,
cacheBusterValue,
furtherOptions,
ratioMode,
upScale
);
furtherOptions
);
/// <summary>
/// Gets the underlying image processing service URL from the image path.
/// </summary>
/// <param name="imageUrl">
/// The image URL.
/// </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 the underlying image processing service 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>
/// <param name="imageUrl">The image URL.</param>
/// <param name="cropDataSet">The crop data set.</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 the underlying image processing service supports. For example:
/// <example><![CDATA[
/// furtherOptions: "bgcolor=fff"
/// ]]></example></param>
/// <returns>
/// The the URL of the cropped image.
/// The URL of the cropped image.
/// </returns>
public static string GetCropUrl(
this string imageUrl,
@@ -315,10 +199,7 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
string cacheBusterValue = null,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true,
string animationProcessMode = null)
string furtherOptions = null)
=> imageUrl.GetCropUrl(
ImageUrlGenerator,
cropDataSet,
@@ -330,10 +211,7 @@ namespace Umbraco.Extensions
preferFocalPoint,
useCropDimensions,
cacheBusterValue,
furtherOptions,
ratioMode,
upScale,
animationProcessMode
furtherOptions
);
@@ -341,10 +219,6 @@ namespace Umbraco.Extensions
public static string GetLocalCropUrl(
this MediaWithCrops mediaWithCrops,
string alias,
string cacheBusterValue = null)
{
return mediaWithCrops.LocalCrops.Src +
mediaWithCrops.LocalCrops.GetCropUrl(alias, ImageUrlGenerator, cacheBusterValue: cacheBusterValue);
}
string cacheBusterValue = null) => mediaWithCrops.LocalCrops.Src + mediaWithCrops.LocalCrops.GetCropUrl(alias, ImageUrlGenerator, cacheBusterValue: cacheBusterValue);
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Umbraco.Cms.Core;
@@ -13,17 +13,13 @@ namespace Umbraco.Extensions
public static class ImageCropperTemplateCoreExtensions
{
/// <summary>
/// Gets the underlying image processing service URL by the crop alias (from the "umbracoFile" property alias) on the IPublishedContent item
/// Gets the underlying image processing service 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>
/// <param name="imageUrlGenerator">The image url generator.</param>
/// <param name="mediaItem">The IPublishedContent item.</param>
/// <param name="cropAlias">The crop alias e.g. thumbnail.</param>
/// <param name="imageUrlGenerator">The image URL generator.</param>
/// <param name="publishedValueFallback">The published value fallback.</param>
/// <param name="publishedUrlProvider">The published url provider.</param>
/// <param name="publishedUrlProvider">The published URL provider.</param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
@@ -32,20 +28,14 @@ namespace Umbraco.Extensions
string cropAlias,
IImageUrlGenerator imageUrlGenerator,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider)
{
return mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true);
}
IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true);
public static string GetCropUrl(
this MediaWithCrops mediaWithCrops,
string cropAlias,
IImageUrlGenerator imageUrlGenerator,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider)
{
return mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true);
}
IPublishedUrlProvider publishedUrlProvider) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true);
/// <summary>
/// Gets the crop URL by using only the specified <paramref name="imageCropperValue" />.
@@ -54,6 +44,8 @@ namespace Umbraco.Extensions
/// <param name="imageCropperValue">The image cropper value.</param>
/// <param name="cropAlias">The crop alias.</param>
/// <param name="imageUrlGenerator">The image URL generator.</param>
/// <param name="publishedValueFallback">The published value fallback.</param>
/// <param name="publishedUrlProvider">The published URL provider.</param>
/// <returns>
/// The image crop URL.
/// </returns>
@@ -63,26 +55,17 @@ namespace Umbraco.Extensions
string cropAlias,
IImageUrlGenerator imageUrlGenerator,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider)
{
return mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, imageCropperValue, true, cropAlias: cropAlias, useCropDimensions: true);
}
IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, imageCropperValue, true, cropAlias: cropAlias, useCropDimensions: true);
/// <summary>
/// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper Json data on the IPublishedContent item.
/// Gets the underlying image processing service 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>
/// <param name="imageUrlGenerator">The image url generator.</param>
/// <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>
/// <param name="imageUrlGenerator">The image URL generator.</param>
/// <param name="publishedValueFallback">The published value fallback.</param>
/// <param name="publishedUrlProvider">The published url provider.</param>
/// <param name="publishedUrlProvider">The published URL provider.</param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
@@ -92,74 +75,36 @@ namespace Umbraco.Extensions
string cropAlias,
IImageUrlGenerator imageUrlGenerator,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider)
{
return mediaItem.GetCropUrl( imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true);
}
IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true);
public static string GetCropUrl(this MediaWithCrops mediaWithCrops,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider,
string propertyAlias,
string cropAlias,
IImageUrlGenerator imageUrlGenerator)
{
return mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true);
}
IImageUrlGenerator imageUrlGenerator) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true);
/// <summary>
/// Gets the underlying image processing service URL from the IPublishedContent item.
/// </summary>
/// <param name="mediaItem">
/// The IPublishedContent item.
/// </param>
/// <param name="imageUrlGenerator">The image url generator.</param>
/// <param name="mediaItem">The IPublishedContent item.</param>
/// <param name="imageUrlGenerator">The image URL generator.</param>
/// <param name="publishedValueFallback">The published value fallback.</param>
/// <param name="publishedUrlProvider">The published url provider.</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>
/// <param name="publishedUrlProvider">The published URL provider.</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>
/// <returns>
/// The URL of the cropped image.
/// </returns>
@@ -178,12 +123,7 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true)
{
return mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, upScale);
}
string furtherOptions = null) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions);
public static string GetCropUrl(
this MediaWithCrops mediaWithCrops,
@@ -200,13 +140,14 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true)
string furtherOptions = null)
{
if (mediaWithCrops == null) throw new ArgumentNullException(nameof(mediaWithCrops));
if (mediaWithCrops == null)
{
throw new ArgumentNullException(nameof(mediaWithCrops));
}
return mediaWithCrops.Content.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, mediaWithCrops.LocalCrops, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, upScale);
return mediaWithCrops.Content.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, mediaWithCrops.LocalCrops, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions);
}
private static string GetCropUrl(
@@ -226,16 +167,17 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true)
string furtherOptions = null)
{
if (mediaItem == null) throw new ArgumentNullException(nameof(mediaItem));
var cacheBusterValue = cacheBuster ? mediaItem.UpdateDate.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture) : null;
if (mediaItem == null)
{
throw new ArgumentNullException(nameof(mediaItem));
}
if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false)
return string.Empty;
{
return null;
}
var mediaItemUrl = mediaItem.MediaUrl(publishedUrlProvider, propertyAlias: propertyAlias);
@@ -269,63 +211,34 @@ namespace Umbraco.Extensions
}
}
var cacheBusterValue = cacheBuster ? mediaItem.UpdateDate.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture) : null;
return GetCropUrl(
mediaItemUrl, imageUrlGenerator, localCrops, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions,
cacheBusterValue, furtherOptions, ratioMode, upScale);
cacheBusterValue, furtherOptions);
}
/// <summary>
/// Gets the underlying image processing service 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 the underlying image processing service 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>
/// <param name="imageUrl">The image URL.</param>
/// <param name="imageUrlGenerator">The image URL generator.</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 the underlying image processing service supports. For example:
/// <example><![CDATA[
/// furtherOptions: "bgcolor=fff"
/// ]]></example></param>
/// <returns>
/// The the URL of the cropped image.
/// The URL of the cropped image.
/// </returns>
public static string GetCropUrl(
this string imageUrl,
@@ -340,11 +253,12 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
string cacheBusterValue = null,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true)
string furtherOptions = null)
{
if (string.IsNullOrEmpty(imageUrl)) return string.Empty;
if (string.IsNullOrWhiteSpace(imageUrl))
{
return null;
}
ImageCropperValue cropDataSet = null;
if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null))
@@ -354,62 +268,30 @@ namespace Umbraco.Extensions
return GetCropUrl(
imageUrl, imageUrlGenerator, cropDataSet, width, height, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale);
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions);
}
/// <summary>
/// Gets the underlying image processing service 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 the underlying image processing service 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>
/// <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">The crop data set.</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 the underlying image processing service supports. For example:
/// <example><![CDATA[
/// furtherOptions: "bgcolor=fff"
/// ]]></example></param>
/// <returns>
/// The <see cref="string"/>.
/// The URL of the cropped image.
/// </returns>
public static string GetCropUrl(
this string imageUrl,
@@ -424,72 +306,57 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
string cacheBusterValue = null,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true,
string animationProcessMode = null)
string furtherOptions = null)
{
if (string.IsNullOrEmpty(imageUrl)) return string.Empty;
if (string.IsNullOrWhiteSpace(imageUrl))
{
return null;
}
ImageUrlGenerationOptions options;
if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null))
{
var crop = cropDataSet.GetCrop(cropAlias);
ImageCropperValue.ImageCropperCrop crop = cropDataSet.GetCrop(cropAlias);
// if a crop was specified, but not found, return 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);
options = cropDataSet.GetCropBaseOptions(imageUrl, crop, preferFocalPoint || string.IsNullOrWhiteSpace(cropAlias));
if (crop != null & useCropDimensions)
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)
// Calculate missing dimension if a predefined crop has been specified, but has no coordinates
if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == 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;
if (width != null && height == null)
{
height = (int)MathF.Round(width.Value * ((float)crop.Height / crop.Width));
}
else if (width == null && height != null)
{
width = (int)MathF.Round(height.Value * ((float)crop.Width / crop.Height));
}
}
}
else
{
options = new ImageUrlGenerationOptions (imageUrl)
options = new ImageUrlGenerationOptions(imageUrl)
{
ImageCropMode = (imageCropMode ?? ImageCropMode.Pad),
ImageCropMode = (imageCropMode ?? ImageCropMode.Pad), // Not sure why we default to Pad
ImageCropAnchor = imageCropAnchor
};
}
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)
{
// 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.Width = width;
options.Height = height;
options.FurtherOptions = furtherOptions;
options.CacheBusterValue = cacheBusterValue;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -246,8 +246,6 @@ namespace Umbraco.Extensions
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true,
bool htmlEncode = true)
{
if (mediaItem == null)
@@ -256,8 +254,8 @@ namespace Umbraco.Extensions
}
var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode,
upScale);
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions);
return CreateHtmlString(url, htmlEncode);
}
@@ -273,16 +271,14 @@ namespace Umbraco.Extensions
bool useCropDimensions = true,
string cacheBusterValue = null,
string furtherOptions = null,
ImageCropRatioMode? ratioMode = null,
bool upScale = true,
bool htmlEncode = true)
{
if (imageCropperValue == null) return HtmlString.Empty;
var imageUrl = imageCropperValue.Src;
var url = imageUrl.GetCropUrl(imageCropperValue, width, height, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode,
upScale);
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions);
return CreateHtmlString(url, htmlEncode);
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Web;
using SixLabors.ImageSharp.Web.Commands;
using SixLabors.ImageSharp.Web.Processors;
namespace Umbraco.Cms.Web.Common.ImageProcessors
{
/// <summary>
/// Allows the cropping of images.
/// </summary>
public class CropWebProcessor : IImageWebProcessor
{
/// <summary>
/// The command constant for the crop coordinates.
/// </summary>
public const string Coordinates = "cc";
/// <inheritdoc/>
public IEnumerable<string> Commands { get; } = new[]
{
Coordinates
};
/// <inheritdoc/>
public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary<string, string> commands, CommandParser parser, CultureInfo culture)
{
RectangleF? coordinates = GetCoordinates(commands, parser, culture);
if (coordinates != null)
{
// Convert the coordinates to a pixel based rectangle
int sourceWidth = image.Image.Width;
int sourceHeight = image.Image.Height;
int x = (int)MathF.Round(coordinates.Value.X * sourceWidth);
int y = (int)MathF.Round(coordinates.Value.Y * sourceHeight);
int width = (int)MathF.Round(coordinates.Value.Width * sourceWidth);
int height = (int)MathF.Round(coordinates.Value.Height * sourceHeight);
var cropRectangle = new Rectangle(x, y, width, height);
image.Image.Mutate(x => x.Crop(cropRectangle));
}
return image;
}
private static RectangleF? GetCoordinates(IDictionary<string, string> commands, CommandParser parser, CultureInfo culture)
{
float[] coordinates = parser.ParseValue<float[]>(commands.GetValueOrDefault(Coordinates), culture);
if (coordinates.Length != 4)
{
return null;
}
// The right and bottom values are actually the distance from those sides, so convert them into real coordinates
return RectangleF.FromLTRB(coordinates[0], coordinates[1], 1 - coordinates[2], 1 - coordinates[3]);
}
}
}

View File

@@ -180,6 +180,7 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
{
if (disposing)
{
_inMemoryModelFactory.ModelsChanged -= InMemoryModelFactoryModelsChanged;
_locker.Dispose();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/**
/**
* @ngdoc service
* @name umbraco.resources.imageUrlGeneratorResource
* @function
@@ -11,14 +11,14 @@
function imageUrlGeneratorResource($http, umbRequestHelper) {
function getCropUrl(mediaPath, width, height, imageCropMode, animationProcessMode) {
function getCropUrl(mediaPath, width, height, imageCropMode) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"imageUrlGeneratorApiBaseUrl",
"GetCropUrl",
{ mediaPath, width, height, imageCropMode, animationProcessMode })),
{ mediaPath, width, height, imageCropMode })),
'Failed to get crop URL');
}

View File

@@ -1,4 +1,4 @@
/**
/**
* @ngdoc service
* @name umbraco.services.mediaHelper
* @description A helper object used for dealing with media items
@@ -408,16 +408,20 @@ function mediaHelper(umbRequestHelper, $http, $log) {
* @param {string} imagePath Raw image path
* @param {object} options Object describing image generation parameters:
* {
* animationProcessMode: <string>
* cacheBusterValue: <string>
* width: <int>
* height: <int>
* focalPoint: {
* left: <int>
* top: <int>
* },
* height: <int>
* mode: <string>
* upscale: <boolean>
* width: <int>
* cacheBusterValue: <string>
* crop: {
* x1: <int>
* x2: <int>
* y1: <int>
* y2: <int>
* },
* }
*/
getProcessedImageUrl: function (imagePath, options) {
@@ -433,18 +437,16 @@ function mediaHelper(umbRequestHelper, $http, $log) {
"GetProcessedImageUrl",
{
imagePath,
animationProcessMode: options.animationProcessMode,
cacheBusterValue: options.cacheBusterValue,
width: options.width,
height: options.height,
focalPointLeft: options.focalPoint ? options.focalPoint.left : null,
focalPointTop: options.focalPoint ? options.focalPoint.top : null,
height: options.height,
mode: options.mode,
upscale: options.upscale || false,
width: options.width,
cacheBusterValue: options.cacheBusterValue,
cropX1: options.crop ? options.crop.x1 : null,
cropX2: options.crop ? options.crop.x2 : null,
cropY1: options.crop ? options.crop.y1 : null,
cropY2: options.crop ? options.crop.y : null
cropY2: options.crop ? options.crop.y2 : null
})),
"Failed to retrieve processed image URL for image: " + imagePath);
}

View File

@@ -306,8 +306,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
if (imgUrl) {
mediaHelper.getProcessedImageUrl(imgUrl,
{
height: newSize.height,
width: newSize.width
width: newSize.width,
height: newSize.height
})
.then(function (resizedImgUrl) {
editor.dom.setAttrib(imageDomElement, 'data-mce-src', resizedImgUrl);
@@ -1526,15 +1526,13 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
args.editor.on('ObjectResized', function (e) {
var srcAttr = $(e.target).attr("src");
var path = srcAttr.split("?")[0];
mediaHelper.getProcessedImageUrl(path,
{
height: e.height,
moded: "max",
width: e.width
})
.then(function (resizedPath) {
$(e.target).attr("data-mce-src", resizedPath);
});
mediaHelper.getProcessedImageUrl(path, {
width: e.width,
height: e.height,
mode: "max"
}).then(function (resizedPath) {
$(e.target).attr("data-mce-src", resizedPath);
});
syncContent();
});

View File

@@ -6,6 +6,7 @@
}
.umb-expansion-panel__header {
box-sizing: border-box;
padding: 10px 20px;
font-weight: bold;
display: flex;

View File

@@ -42,7 +42,7 @@
var path = umbRequestHelper.convertVirtualToAbsolutePath(vm.blockConfigModel.thumbnail);
if (path.toLowerCase().endsWith(".svg") === false) {
path += "?upscale=false&width=400";
path += "?width=400";
}
vm.styleBackgroundImage = 'url(\''+path+'\')';
}

View File

@@ -1,4 +1,4 @@
(function () {
(function () {
'use strict';
/**
@@ -55,7 +55,7 @@
if (thumbnail) {
if (mediaHelper.detectIfImageByExtension(property.value)) {
//get default big thumbnail from image processor
var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first";
var thumbnailUrl = property.value + "?width=500&rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss");
return thumbnailUrl;
}
else {

View File

@@ -70,28 +70,25 @@ angular.module("umbraco")
if ($scope.control.value.coordinates) {
// New way, crop by percent must come before width/height.
var coords = $scope.control.value.coordinates;
url += `?crop=${coords.x1},${coords.y1},${coords.x2},${coords.y2}&cropmode=percentage`;
url += `?cc=${coords.x1},${coords.y1},${coords.x2},${coords.y2}`;
} else {
// Here in order not to break existing content where focalPoint were used.
// For some reason width/height have to come first when mode=crop.
if ($scope.control.value.focalPoint) {
url += `?center=${$scope.control.value.focalPoint.top},${$scope.control.value.focalPoint.left}`;
url += '&mode=crop';
url += `?rxy=${$scope.control.value.focalPoint.left},${$scope.control.value.focalPoint.top}`;
} else {
// Prevent black padding and no crop when focal point not set / changed from default
url += '?center=0.5,0.5&mode=crop';
url += '?rxy=0.5,0.5';
}
}
url += '&width=' + $scope.control.editor.config.size.width;
url += '&height=' + $scope.control.editor.config.size.height;
url += '&animationprocessmode=first';
}
// set default size if no crop present (moved from the view)
if (url.includes('?') === false)
{
url += '?width=800&upscale=false&animationprocessmode=false'
url += '?width=800'
}
return url;

View File

@@ -234,7 +234,7 @@ angular.module('umbraco')
if (property.value && property.value.src) {
if (thumbnail === true) {
return property.value.src + "?width=500&mode=max&animationprocessmode=first";
return property.value.src + "?width=500";
}
else {
return property.value.src;

View File

@@ -84,6 +84,7 @@
<PropertyGroup>
<BellePath>$(ProjectDir)wwwroot/umbraco</BellePath>
<JsonSchemaPath>$(ProjectDir)umbraco/config/appsettings-schema.json</JsonSchemaPath>
</PropertyGroup>
<Target Name="CheckPreconditions" BeforeTargets="Build">
<Message Text="-CheckPreconditions-" Importance="high" />
@@ -100,6 +101,11 @@
<Message Text="Generate the appsettings json schema." Importance="High" Condition="!Exists('$(JsonSchemaPath)') and '$(UmbracoBuild)' == ''" />
<CallTarget Targets="JsonSchemaBuild" Condition="!Exists('$(JsonSchemaPath)') and '$(UmbracoBuild)' == ''" />
<CallTarget Targets="AppsettingsBuild" Condition="!Exists('appsettings.json')" />
<CallTarget Targets="AppsettingsDevBuild" Condition="!Exists('appsettings.Development.json')" />
</Target>
<Target Name="BelleBuild">
<!-- <Exec WorkingDirectory="$(ProjectDir)/../../src/Umbraco.Web.UI.Client/" Command="powershell -ExecutionPolicy RemoteSigned -Command '&amp;{ npm install ; npm run build }'" />-->
@@ -107,6 +113,15 @@
<Target Name="JsonSchemaBuild">
<!-- <Exec WorkingDirectory="$(ProjectDir)/../../" Command="powershell -ExecutionPolicy RemoteSigned -Command '&amp;dotnet run &#45;&#45;project $pwd/src/JsonSchema/JsonSchema.csproj -c Release &#45;&#45; &#45;&#45;outputFile $pwd/src/Umbraco.Web.UI.NetCore/$(JsonSchemaPath)'" />-->
</Target>
<Target Name="AppsettingsBuild">
<Message Text="Generating appsettings.json because it doesnt exist" Importance="High" />
<Copy SourceFiles="$(Projectdir)/appsettings.template.json" DestinationFiles="$(ProjectDir)/appsettings.json" />
</Target>
<Target Name="AppsettingsDevBuild">
<Message Text="Generating appsettings.Development.json because it doesnt exist" Importance="High" />
<Copy SourceFiles="$(ProjectDir)appsettings.Development.template.json" DestinationFiles="$(ProjectDir)appsettings.Development.json" />
</Target>
<!-- clean Belle when cleaning and rebuilding, but only in Visual Studio -->
<Target Name="CleanPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">