Merge remote-tracking branch 'origin/v9/dev' into v9/feature/language-keys-cleanup

This commit is contained in:
Elitsa Marinovska
2021-09-01 08:40:00 +02:00
32 changed files with 476 additions and 130 deletions

View File

@@ -42,4 +42,8 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# Needing to set unsafe-perm as root is the user setup
# https://docs.npmjs.com/cli/v6/using-npm/config#unsafe-perm
# Default: false if running as root, true otherwise (we are ROOT)
RUN npm -g config set user vscode && npm -g config set unsafe-perm
RUN npm -g config set user vscode && npm -g config set unsafe-perm
# Generate and trust a local developer certificate for Kestrel
# This is needed for Kestrel to bind on https
RUN dotnet dev-certs https --trust

View File

@@ -2,7 +2,7 @@ version: '3'
services:
app:
build:
build:
context: .
dockerfile: Dockerfile
args:
@@ -18,7 +18,7 @@ services:
volumes:
- ..:/workspace:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
@@ -28,7 +28,7 @@ services:
# Uncomment the next line to use a non-root user for all processes.
# user: vscode
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
# DotNetCore ENV Variables
@@ -41,7 +41,7 @@ services:
- Umbraco__CMS__Unattended__UnattendedUserPassword=password1234
- Umbraco__CMS__Global__Smtp__Host=smtp4dev
- Umbraco__CMS__Global__Smtp__Port=25
- Umbraco__CMS__Global__Smtp__From=warren-env@umbraco.com
- Umbraco__CMS__Global__Smtp__From=noreply@umbraco.test
db:
image: mcr.microsoft.com/mssql/server:2019-latest

2
.gitattributes vendored
View File

@@ -1,3 +1,4 @@
* text=auto
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
@@ -44,6 +45,7 @@
*.json text=auto
*.xml text=auto
*.resx text=auto
*.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.csproj text=auto merge=union
*.vbproj text=auto merge=union

View File

@@ -0,0 +1,29 @@
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<runtime xdt:Transform="InsertIfMissing" />
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" xdt:Transform="InsertIfMissing" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Collections.Immutable')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Buffers')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Memory')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Numerics.Vectors')" xdt:Transform="Remove" />
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.5.0" newVersion="1.2.5.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" />
<bindingRedirect oldVersion="4.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" />
<bindingRedirect oldVersion="4.0.0.0-4.0.1.1" newVersion="4.0.1.1" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" />
<bindingRedirect oldVersion="4.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -92,6 +92,7 @@
$src = "$($this.SolutionRoot)\src"
$log = "$($this.BuildTemp)\belle.log"
Write-Host "Compile Belle"
Write-Host "Logging to $log"
@@ -555,7 +556,6 @@
# run
if (-not $get)
{
cd
if ($command.Length -eq 0)
{
$command = @( "Build" )

View File

@@ -1,7 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">Umbraco.Cms.Web.UI</RootNamespace>
<DefaultItemExcludes>$(DefaultItemExcludes);App_Plugins/**;</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);umbraco/**;</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);wwwroot/media/**;</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
@@ -10,42 +14,18 @@
<PackageReference Include="Umbraco.SqlServerCE" Version="4.0.0.1" Condition="'$(UseSqlCe)' == 'true'" />
</ItemGroup>
<Import Project="..\PackageTestSiteName\build\PackageTestSiteName.targets" Condition="'$(PackageTestSiteName)' != ''"/>
<Import Project="..\PackageTestSiteName\build\PackageTestSiteName.targets" Condition="'$(PackageTestSiteName)' != ''" />
<ItemGroup Condition="'$(PackageTestSiteName)' != ''">
<ProjectReference Include="..\PackageTestSiteName\PackageTestSiteName.csproj"/>
<ProjectReference Include="..\PackageTestSiteName\PackageTestSiteName.csproj" />
</ItemGroup>
<ItemGroup>
<Content Remove="umbraco\Data\**" />
<Content Remove="umbraco\logs\**" />
<Content Remove="umbraco\MediaCache\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="umbraco\Data\**" />
<Compile Remove="umbraco\logs\**" />
<Compile Remove="umbraco\MediaCache\**" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Remove="umbraco\Data\**" />
<EmbeddedResource Remove="umbraco\logs\**" />
<EmbeddedResource Remove="umbraco\MediaCache\**" />
</ItemGroup>
<ItemGroup>
<None Include="config\**\*.*">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</None>
<None Include="umbraco\**\*.*">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</None>
<None Include="App_Plugins\**\*.*">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</None>
<None Remove="umbraco\Data\**" />
<None Remove="umbraco\logs\**" />
<None Remove="umbraco\MediaCache\**" />
<Content Include="App_Plugins/**" CopyToOutputDirectory="Always" />
<Content Include="umbraco/**" CopyToOutputDirectory="Always" />
<Content Remove="umbraco/Data/**" />
<Content Remove="umbraco/Logs/**" />
<Content Remove="umbraco/mediacache/**"/>
</ItemGroup>
<!-- Set this to true if ModelsBuilder mode is not InMemoryAuto-->
@@ -53,4 +33,5 @@
<RazorCompileOnBuild>false</RazorCompileOnBuild>
<RazorCompileOnPublish>false</RazorCompileOnPublish>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
namespace Umbraco.Cms.Core.Cache
{
public interface IValueEditorCache
{
public IDataValueEditor GetValueEditor(IDataEditor dataEditor, IDataType dataType);
public void ClearCache(IEnumerable<int> dataTypeIds);
}
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
namespace Umbraco.Cms.Core.Cache
{
public class ValueEditorCache : IValueEditorCache
{
private readonly Dictionary<string, Dictionary<int, IDataValueEditor>> _valueEditorCache;
private readonly object _dictionaryLocker;
public ValueEditorCache()
{
_valueEditorCache = new Dictionary<string, Dictionary<int, IDataValueEditor>>();
_dictionaryLocker = new object();
}
public IDataValueEditor GetValueEditor(IDataEditor editor, IDataType dataType)
{
// Lock just in case multiple threads uses the cache at the same time.
lock (_dictionaryLocker)
{
// We try and get the dictionary based on the IDataEditor alias,
// this is here just in case a data type can have more than one value data editor.
// If this is not the case this could be simplified quite a bit, by just using the inner dictionary only.
IDataValueEditor valueEditor;
if (_valueEditorCache.TryGetValue(editor.Alias, out Dictionary<int, IDataValueEditor> dataEditorCache))
{
if (dataEditorCache.TryGetValue(dataType.Id, out valueEditor))
{
return valueEditor;
}
valueEditor = editor.GetValueEditor(dataType.Configuration);
dataEditorCache[dataType.Id] = valueEditor;
return valueEditor;
}
valueEditor = editor.GetValueEditor(dataType.Configuration);
_valueEditorCache[editor.Alias] = new Dictionary<int, IDataValueEditor> { [dataType.Id] = valueEditor };
return valueEditor;
}
}
public void ClearCache(IEnumerable<int> dataTypeIds)
{
lock (_dictionaryLocker)
{
// If a datatype is saved or deleted we have to clear any value editors based on their ID from the cache,
// since it could mean that their configuration has changed.
foreach (var id in dataTypeIds)
{
foreach (Dictionary<int, IDataValueEditor> editors in _valueEditorCache.Values)
{
editors.Remove(id);
}
}
}
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.Cache
{
public sealed class ValueEditorCacheRefresher : PayloadCacheRefresherBase<DataTypeCacheRefresherNotification, DataTypeCacheRefresher.JsonPayload>
{
private readonly IValueEditorCache _valueEditorCache;
public ValueEditorCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
IEventAggregator eventAggregator,
ICacheRefresherNotificationFactory factory,
IValueEditorCache valueEditorCache) : base(appCaches, serializer, eventAggregator, factory)
{
_valueEditorCache = valueEditorCache;
}
public static readonly Guid UniqueId = Guid.Parse("D28A1DBB-2308-4918-9A92-2F8689B6CBFE");
public override Guid RefresherUniqueId => UniqueId;
public override string Name => "ValueEditorCacheRefresher";
public override void Refresh(DataTypeCacheRefresher.JsonPayload[] payloads)
{
IEnumerable<int> ids = payloads.Select(x => x.Id);
_valueEditorCache.ClearCache(ids);
}
// these events should never trigger
// everything should be PAYLOAD/JSON
public override void RefreshAll()
{
throw new NotSupportedException();
}
public override void Refresh(int id)
{
throw new NotSupportedException();
}
public override void Refresh(Guid id)
{
throw new NotSupportedException();
}
public override void Remove(int id)
{
throw new NotSupportedException();
}
}
}

View File

@@ -253,6 +253,9 @@ namespace Umbraco.Cms.Core.DependencyInjection
// register a basic/noop published snapshot service to be replaced
Services.AddSingleton<IPublishedSnapshotService, InternalPublishedSnapshotService>();
// Register ValueEditorCache used for validation
Services.AddSingleton<IValueEditorCache, ValueEditorCache>();
}
}
}

View File

@@ -258,16 +258,17 @@ namespace Umbraco.Cms.Core.IO
// test URL
var path = fullPathOrUrl.Replace('\\', '/'); // ensure URL separator char
// if it starts with the root path, strip it and trim the starting slash to make it relative
// eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg"
// or on unix systems "/var/wwwroot/test/Meia/1234/img.jpg"
if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/'))
return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash);
// if it starts with the root URL, strip it and trim the starting slash to make it relative
// eg "/Media/1234/img.jpg" => "1234/img.jpg"
if (_ioHelper.PathStartsWith(path, _rootUrl, '/'))
return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash);
// if it starts with the root path, strip it and trim the starting slash to make it relative
// eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg"
if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/'))
return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash);
// unchanged - what else?
return path;
}

View File

@@ -1,16 +0,0 @@
using System.IO;
using Umbraco.Cms.Core.Hosting;
namespace Umbraco.Cms.Core.IO
{
public class SystemFiles
{
public static string TinyMceConfig => Constants.SystemDirectories.Config + "/tinyMceConfig.config";
// TODO: Kill this off we don't have umbraco.config XML cache we now have NuCache
public static string GetContentCacheXml(IHostingEnvironment hostingEnvironment)
{
return Path.Combine(hostingEnvironment.LocalTempPath, "umbraco.config");
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models
@@ -15,9 +15,9 @@ namespace Umbraco.Cms.Core.Models
/// </summary>
/// <param name="name">name of the Media object</param>
/// <param name="parent">Parent <see cref="IMedia"/> object</param>
/// <param name="contentType">MediaType for the current Media object</param>
public Media(string name, IMedia parent, IMediaType contentType)
: this(name, parent, contentType, new PropertyCollection())
/// <param name="mediaType">MediaType for the current Media object</param>
public Media(string name, IMedia parent, IMediaType mediaType)
: this(name, parent, mediaType, new PropertyCollection())
{ }
/// <summary>
@@ -25,10 +25,10 @@ namespace Umbraco.Cms.Core.Models
/// </summary>
/// <param name="name">name of the Media object</param>
/// <param name="parent">Parent <see cref="IMedia"/> object</param>
/// <param name="contentType">MediaType for the current Media object</param>
/// <param name="mediaType">MediaType for the current Media object</param>
/// <param name="properties">Collection of properties</param>
public Media(string name, IMedia parent, IMediaType contentType, IPropertyCollection properties)
: base(name, parent, contentType, properties)
public Media(string name, IMedia parent, IMediaType mediaType, IPropertyCollection properties)
: base(name, parent, mediaType, properties)
{ }
/// <summary>
@@ -36,9 +36,9 @@ namespace Umbraco.Cms.Core.Models
/// </summary>
/// <param name="name">name of the Media object</param>
/// <param name="parentId">Id of the Parent IMedia</param>
/// <param name="contentType">MediaType for the current Media object</param>
public Media(string name, int parentId, IMediaType contentType)
: this(name, parentId, contentType, new PropertyCollection())
/// <param name="mediaType">MediaType for the current Media object</param>
public Media(string name, int parentId, IMediaType mediaType)
: this(name, parentId, mediaType, new PropertyCollection())
{ }
/// <summary>
@@ -46,10 +46,10 @@ namespace Umbraco.Cms.Core.Models
/// </summary>
/// <param name="name">Name of the Media object</param>
/// <param name="parentId">Id of the Parent IMedia</param>
/// <param name="contentType">MediaType for the current Media object</param>
/// <param name="mediaType">MediaType for the current Media object</param>
/// <param name="properties">Collection of properties</param>
public Media(string name, int parentId, IMediaType contentType, IPropertyCollection properties)
: base(name, parentId, contentType, properties)
public Media(string name, int parentId, IMediaType mediaType, IPropertyCollection properties)
: base(name, parentId, mediaType, properties)
{ }
/// <summary>
@@ -57,9 +57,9 @@ namespace Umbraco.Cms.Core.Models
/// </summary>
/// <param name="contentType">New MediaType for this Media</param>
/// <remarks>Leaves PropertyTypes intact after change</remarks>
internal void ChangeContentType(IMediaType contentType)
internal void ChangeContentType(IMediaType mediaType)
{
ChangeContentType(contentType, false);
ChangeContentType(mediaType, false);
}
/// <summary>
@@ -68,14 +68,14 @@ namespace Umbraco.Cms.Core.Models
/// </summary>
/// <param name="contentType">New MediaType for this Media</param>
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
internal void ChangeContentType(IMediaType contentType, bool clearProperties)
internal void ChangeContentType(IMediaType mediaType, bool clearProperties)
{
ChangeContentType(new SimpleContentType(contentType));
ChangeContentType(new SimpleContentType(mediaType));
if (clearProperties)
Properties.EnsureCleanPropertyTypes(contentType.CompositionPropertyTypes);
Properties.EnsureCleanPropertyTypes(mediaType.CompositionPropertyTypes);
else
Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes);
Properties.EnsurePropertyTypes(mediaType.CompositionPropertyTypes);
Properties.ClearCollectionChangedEvents(); // be sure not to double add
Properties.CollectionChanged += PropertiesChanged;

View File

@@ -21,7 +21,6 @@ namespace Umbraco.Cms.Core.PropertyEditors
public class DataEditor : IDataEditor
{
private IDictionary<string, object> _defaultConfiguration;
private IDataValueEditor _reusableEditor;
/// <summary>
/// Initializes a new instance of the <see cref="DataEditor"/> class.
@@ -90,8 +89,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
/// simple enough for now.</para>
/// </remarks>
// TODO: point of that one? shouldn't we always configure?
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_reusableEditor ?? (_reusableEditor = CreateValueEditor()));
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor();
/// <inheritdoc />
/// <remarks>

View File

@@ -130,6 +130,7 @@ namespace Umbraco.Cms.Core.Cache
{
_distributedCache.RefreshDataTypeCache(entity);
}
_distributedCache.RefreshValueEditorCache(notification.SavedEntities);
}
public void Handle(DataTypeDeletedNotification notification)
@@ -138,6 +139,7 @@ namespace Umbraco.Cms.Core.Cache
{
_distributedCache.RemoveDataTypeCache(entity);
}
_distributedCache.RefreshValueEditorCache(notification.DeletedEntities);
}
#endregion

View File

@@ -1,6 +1,7 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
@@ -106,6 +107,21 @@ namespace Umbraco.Extensions
#endregion
#region ValueEditorCache
public static void RefreshValueEditorCache(this DistributedCache dc, IEnumerable<IDataType> dataTypes)
{
if (dataTypes is null)
{
return;
}
var payloads = dataTypes.Select(x => new DataTypeCacheRefresher.JsonPayload(x.Id, x.Key, false));
dc.RefreshByPayload(ValueEditorCacheRefresher.UniqueId, payloads);
}
#endregion
#region ContentCache
public static void RefreshAllContentCache(this DistributedCache dc)

View File

@@ -33,7 +33,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
/// <summary>
/// Initializes a new instance of the <see cref="KeepAlive"/> class.
/// </summary>
/// <param name="requestAccessor">Accessor for the current request.</param>
/// <param name="hostingEnvironment">The current hosting environment</param>
/// <param name="mainDom">Representation of the main application domain.</param>
/// <param name="keepAliveSettings">The configuration for keep alive settings.</param>
/// <param name="logger">The typed logger.</param>
@@ -86,7 +86,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
using (_profilingLogger.DebugDuration<KeepAlive>("Keep alive executing", "Keep alive complete"))
{
var umbracoAppUrl = _hostingEnvironment.ApplicationMainUrl.ToString();
var umbracoAppUrl = _hostingEnvironment.ApplicationMainUrl?.ToString();
if (umbracoAppUrl.IsNullOrWhiteSpace())
{
_logger.LogWarning("No umbracoApplicationUrl for service (yet), skip.");

View File

@@ -927,6 +927,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
return p.ExitCode;
}
}
/// <summary>

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Cms.Core.Models.Blocks;
@@ -17,6 +18,12 @@ namespace Umbraco.Cms.Core.PropertyEditors
public class BlockEditorPropertyHandler : ComplexPropertyEditorContentNotificationHandler
{
private readonly BlockListEditorDataConverter _converter = new BlockListEditorDataConverter();
private readonly ILogger _logger;
public BlockEditorPropertyHandler(ILogger<BlockEditorPropertyHandler> logger)
{
_logger = logger;
}
protected override string EditorAlias => Constants.PropertyEditors.Aliases.BlockList;
@@ -110,8 +117,22 @@ namespace Umbraco.Cms.Core.PropertyEditors
// this gets a little ugly because there could be some other complex editor that contains another block editor
// and since we would have no idea how to parse that, all we can do is try JSON Path to find another block editor
// of our type
var json = JToken.Parse(asString);
if (ProcessJToken(json, createGuid, out var result))
JToken json = null;
try
{
json = JToken.Parse(asString);
}
catch (Exception e)
{
// See issue https://github.com/umbraco/Umbraco-CMS/issues/10879
// We are detecting JSON data by seeing if a string is surrounded by [] or {}
// If people enter text like [PLACEHOLDER] JToken parsing fails, it's safe to ignore though
// Logging this just in case in the future we find values that are not safe to ignore
_logger.LogWarning( "The property {PropertyAlias} on content type {ContentTypeKey} has a value of: {BlockItemValue} - this was recognized as JSON but could not be parsed",
data.Key, propertyAliasToBlockItemData.Key, asString);
}
if (json != null && ProcessJToken(json, createGuid, out var result))
{
// need to re-save this back to the RawPropertyValues
data.RawPropertyValues[propertyAliasToBlockItemData.Key] = result;

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Extensions;
@@ -13,12 +14,18 @@ namespace Umbraco.Cms.Core.Services.Implement
private readonly PropertyEditorCollection _propertyEditors;
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizedTextService _textService;
private readonly IValueEditorCache _valueEditorCache;
public PropertyValidationService(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService)
public PropertyValidationService(
PropertyEditorCollection propertyEditors,
IDataTypeService dataTypeService,
ILocalizedTextService textService,
IValueEditorCache valueEditorCache)
{
_propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
_textService = textService;
_valueEditorCache = valueEditorCache;
}
/// <inheritdoc />
@@ -58,7 +65,7 @@ namespace Umbraco.Cms.Core.Services.Implement
_textService.Localize("validation", "invalidPattern"),
};
var valueEditor = editor.GetValueEditor(dataType.Configuration);
IDataValueEditor valueEditor = _valueEditorCache.GetValueEditor(editor, dataType);
foreach (var validationResult in valueEditor.Validate(postedValue, isRequired, validationRegExp))
{
// If we've got custom error messages, we'll replace the default ones that will have been applied in the call to Validate().

View File

@@ -9,6 +9,7 @@ using System.Threading;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
@@ -72,6 +73,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
private IJsonSerializer Serializer => GetRequiredService<IJsonSerializer>();
private IValueEditorCache ValueEditorCache => GetRequiredService<IValueEditorCache>();
[SetUp]
public void Setup() => ContentRepositoryBase.ThrowOnWarning = true;
@@ -1162,7 +1165,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
Assert.IsFalse(content.HasIdentity);
// content cannot publish values because they are invalid
var propertyValidationService = new PropertyValidationService(PropertyEditorCollection, DataTypeService, TextService);
var propertyValidationService = new PropertyValidationService(PropertyEditorCollection, DataTypeService, TextService, ValueEditorCache);
bool isValid = propertyValidationService.IsPropertyDataValid(content, out IProperty[] invalidProperties, CultureImpact.Invariant);
Assert.IsFalse(isValid);
Assert.IsNotEmpty(invalidProperties);

View File

@@ -0,0 +1,133 @@
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache
{
[TestFixture]
public class ValueEditorCacheTests
{
[Test]
public void Caches_ValueEditor()
{
var sut = new ValueEditorCache();
var dataEditor = new FakeDataEditor("TestEditor");
IDataType dataType = CreateDataTypeMock(1).Object;
// Request the same value editor twice
IDataValueEditor firstEditor = sut.GetValueEditor(dataEditor, dataType);
IDataValueEditor secondEditor = sut.GetValueEditor(dataEditor, dataType);
Assert.Multiple(() =>
{
Assert.AreSame(firstEditor, secondEditor);
Assert.AreEqual(1, dataEditor.ValueEditorCount, "GetValueEditor invoked more than once.");
});
}
[Test]
public void Different_Data_Editors_Returns_Different_Value_Editors()
{
var sut = new ValueEditorCache();
var dataEditor1 = new FakeDataEditor("Editor1");
var dataEditor2 = new FakeDataEditor("Editor2");
IDataType dataType = CreateDataTypeMock(1).Object;
IDataValueEditor firstEditor = sut.GetValueEditor(dataEditor1, dataType);
IDataValueEditor secondEditor = sut.GetValueEditor(dataEditor2, dataType);
Assert.AreNotSame(firstEditor, secondEditor);
}
[Test]
public void Different_Data_Types_Returns_Different_Value_Editors()
{
var sut = new ValueEditorCache();
var dataEditor = new FakeDataEditor("Editor");
IDataType dataType1 = CreateDataTypeMock(1).Object;
IDataType dataType2 = CreateDataTypeMock(2).Object;
IDataValueEditor firstEditor = sut.GetValueEditor(dataEditor, dataType1);
IDataValueEditor secondEditor = sut.GetValueEditor(dataEditor, dataType2);
Assert.AreNotSame(firstEditor, secondEditor);
}
[Test]
public void Clear_Cache_Removes_Specific_Editors()
{
var sut = new ValueEditorCache();
var dataEditor1 = new FakeDataEditor("Editor 1");
var dataEditor2 = new FakeDataEditor("Editor 2");
IDataType dataType1 = CreateDataTypeMock(1).Object;
IDataType dataType2 = CreateDataTypeMock(2).Object;
// Load the editors into cache
IDataValueEditor editor1DataType1 = sut.GetValueEditor(dataEditor1, dataType1);
IDataValueEditor editor1Datatype2 = sut.GetValueEditor(dataEditor1, dataType2);
IDataValueEditor editor2DataType1 = sut.GetValueEditor(dataEditor2, dataType1);
IDataValueEditor editor2Datatype2 = sut.GetValueEditor(dataEditor2, dataType2);
sut.ClearCache(new []{dataType1.Id});
// New value editor objects should be created after it's cleared
Assert.AreNotSame(editor1DataType1, sut.GetValueEditor(dataEditor1, dataType1), "Value editor was not cleared from cache");
Assert.AreNotSame(editor2DataType1, sut.GetValueEditor(dataEditor2, dataType1), "Value editor was not cleared from cache");
// But the value editors for data type 2 should be the same
Assert.AreSame(editor1Datatype2, sut.GetValueEditor(dataEditor1, dataType2), "Too many editors was cleared from cache");
Assert.AreSame(editor2Datatype2, sut.GetValueEditor(dataEditor2, dataType2), "Too many editors was cleared from cache");
}
private Mock<IDataType> CreateDataTypeMock(int id)
{
var mock = new Mock<IDataType>();
mock.Setup(x => x.Id).Returns(id);
return mock;
}
/// <summary>
/// A fake IDataEditor
/// </summary>
/// <remarks>
/// This is necessary to ensure that different objects are returned from GetValueEditor
/// </remarks>
private class FakeDataEditor : IDataEditor
{
public FakeDataEditor(string alias)
{
Alias = alias;
}
public string Alias { get; }
public EditorType Type { get; }
public string Name { get; }
public string Icon { get; }
public string Group { get; }
public bool IsDeprecated { get; }
public IDataValueEditor GetValueEditor()
{
ValueEditorCount++;
return Mock.Of<IDataValueEditor>();
}
public IDataValueEditor GetValueEditor(object configuration) => GetValueEditor();
public IDictionary<string, object> DefaultConfiguration { get; }
public IConfigurationEditor GetConfigurationEditor() => throw new System.NotImplementedException();
public IPropertyIndexValueFactory PropertyIndexValueFactory { get; }
public int ValueEditorCount;
}
}
}

View File

@@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
@@ -603,7 +604,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models
return new PropertyValidationService(
propertyEditorCollection,
dataTypeService,
localizedTextService);
localizedTextService,
new ValueEditorCache());
}
}
}

View File

@@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Moq;
using Newtonsoft.Json;
using NUnit.Framework;
using Umbraco.Cms.Core;
@@ -28,11 +30,15 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
private const string SubContentGuid1 = "4c44ce6b3a5c4f5f8f15e3dc24819a9e";
private const string SubContentGuid2 = "a062c06d6b0b44ac892b35d90309c7f8";
private const string SubSettingsGuid1 = "4d998d980ffa4eee8afdc23c4abd6d29";
private static readonly ILogger<BlockEditorPropertyHandler> s_logger = Mock.Of<ILogger<BlockEditorPropertyHandler>>();
[Test]
public void Cannot_Have_Null_Udi()
{
var component = new BlockEditorPropertyHandler();
var component = new BlockEditorPropertyHandler(s_logger);
var json = GetBlockListJson(null, string.Empty);
Assert.Throws<FormatException>(() => component.ReplaceBlockListUdis(json));
}
@@ -48,7 +54,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
var expected = ReplaceGuids(json, guids, ContentGuid1, ContentGuid2, SettingsGuid1);
var component = new BlockEditorPropertyHandler();
var component = new BlockEditorPropertyHandler(s_logger);
var result = component.ReplaceBlockListUdis(json, GuidFactory);
var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings);
@@ -75,7 +81,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
// get the json with the subFeatures as escaped
var json = GetBlockListJson(innerJsonEscaped);
var component = new BlockEditorPropertyHandler();
var component = new BlockEditorPropertyHandler(s_logger);
var result = component.ReplaceBlockListUdis(json, GuidFactory);
// the expected result is that the subFeatures data is no longer escaped
@@ -119,7 +125,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
SubContentGuid2,
SubSettingsGuid1);
var component = new BlockEditorPropertyHandler();
var component = new BlockEditorPropertyHandler(s_logger);
var result = component.ReplaceBlockListUdis(json, GuidFactory);
var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings);
@@ -147,7 +153,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
var json = GetBlockListJson(complexEditorJsonEscaped);
var component = new BlockEditorPropertyHandler();
var component = new BlockEditorPropertyHandler(s_logger);
var result = component.ReplaceBlockListUdis(json, GuidFactory);
// the expected result is that the subFeatures data is no longer escaped

View File

@@ -5,6 +5,7 @@ using System.Threading;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
@@ -45,7 +46,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services
var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor }));
validationService = new PropertyValidationService(propEditors, dataTypeService.Object, Mock.Of<ILocalizedTextService>());
validationService = new PropertyValidationService(propEditors, dataTypeService.Object, Mock.Of<ILocalizedTextService>(), new ValueEditorCache());
}
[Test]

View File

@@ -22,16 +22,18 @@ namespace Umbraco.Extensions
/// </summary>
/// <param name="mediaItem">The IPublishedContent item.</param>
/// <param name="cropAlias">The crop alias e.g. thumbnail.</param>
/// <param name="urlMode">The url mode.</param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
public static string GetCropUrl(
this IPublishedContent mediaItem,
string cropAlias) =>
mediaItem.GetCropUrl(cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider);
string cropAlias,
UrlMode urlMode = UrlMode.Default) =>
mediaItem.GetCropUrl(cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode);
public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string cropAlias)
=> ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider);
public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string cropAlias, UrlMode urlMode = UrlMode.Default)
=> ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode);
/// <summary>
/// Gets the crop URL by using only the specified <paramref name="imageCropperValue" />.
@@ -39,14 +41,16 @@ namespace Umbraco.Extensions
/// <param name="mediaItem">The media item.</param>
/// <param name="imageCropperValue">The image cropper value.</param>
/// <param name="cropAlias">The crop alias.</param>
/// <param name="urlMode">The url mode.</param>
/// <returns>
/// The image crop URL.
/// </returns>
public static string GetCropUrl(
this IPublishedContent mediaItem,
ImageCropperValue imageCropperValue,
string cropAlias)
=> ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, imageCropperValue, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider);
string cropAlias,
UrlMode urlMode = UrlMode.Default)
=> ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, imageCropperValue, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode);
/// <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.
@@ -54,17 +58,19 @@ namespace Umbraco.Extensions
/// <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="urlMode">The url mode.</param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
public static string GetCropUrl(
this IPublishedContent mediaItem,
string propertyAlias,
string cropAlias) =>
mediaItem.GetCropUrl(propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider);
string cropAlias,
UrlMode urlMode = UrlMode.Default) =>
mediaItem.GetCropUrl(propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode);
public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string propertyAlias, string cropAlias)
=> ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider);
public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string propertyAlias, string cropAlias, UrlMode urlMode = UrlMode.Default)
=> ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode);
/// <summary>
/// Gets the underlying image processing service URL from the IPublishedContent item.
@@ -84,6 +90,7 @@ namespace Umbraco.Extensions
/// <example><![CDATA[
/// furtherOptions: "bgcolor=fff"
/// ]]></example></param>
/// <param name="urlMode">The url mode.</param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
@@ -99,7 +106,8 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null)
string furtherOptions = null,
UrlMode urlMode = UrlMode.Default)
=> mediaItem.GetCropUrl(
ImageUrlGenerator,
PublishedValueFallback,
@@ -114,7 +122,8 @@ namespace Umbraco.Extensions
preferFocalPoint,
useCropDimensions,
cacheBuster,
furtherOptions
furtherOptions,
urlMode
);
/// <summary>
@@ -215,7 +224,7 @@ namespace Umbraco.Extensions
);
[Obsolete("Use GetCrop to merge local and media crops, get automatic cache buster value and have more parameters.")]
[Obsolete("Use GetCropUrl to merge local and media crops, get automatic cache buster value and have more parameters.")]
public static string GetLocalCropUrl(
this MediaWithCrops mediaWithCrops,
string alias,

View File

@@ -20,6 +20,7 @@ namespace Umbraco.Extensions
/// <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="urlMode">The url mode.</param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
@@ -28,14 +29,16 @@ namespace Umbraco.Extensions
string cropAlias,
IImageUrlGenerator imageUrlGenerator,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true);
IPublishedUrlProvider publishedUrlProvider,
UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode);
public static string GetCropUrl(
this MediaWithCrops mediaWithCrops,
string cropAlias,
IImageUrlGenerator imageUrlGenerator,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true);
IPublishedUrlProvider publishedUrlProvider,
UrlMode urlMode = UrlMode.Default) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode);
/// <summary>
/// Gets the crop URL by using only the specified <paramref name="imageCropperValue" />.
@@ -46,6 +49,7 @@ namespace Umbraco.Extensions
/// <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="urlMode">The url mode.s</param>
/// <returns>
/// The image crop URL.
/// </returns>
@@ -55,7 +59,8 @@ namespace Umbraco.Extensions
string cropAlias,
IImageUrlGenerator imageUrlGenerator,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, imageCropperValue, true, cropAlias: cropAlias, useCropDimensions: true);
IPublishedUrlProvider publishedUrlProvider,
UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, imageCropperValue, true, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode);
/// <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.
@@ -66,6 +71,7 @@ namespace Umbraco.Extensions
/// <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="urlMode">The url mode.</param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
@@ -75,14 +81,16 @@ namespace Umbraco.Extensions
string cropAlias,
IImageUrlGenerator imageUrlGenerator,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true);
IPublishedUrlProvider publishedUrlProvider,
UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode);
public static string GetCropUrl(this MediaWithCrops mediaWithCrops,
IPublishedValueFallback publishedValueFallback,
IPublishedUrlProvider publishedUrlProvider,
string propertyAlias,
string cropAlias,
IImageUrlGenerator imageUrlGenerator) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true);
IImageUrlGenerator imageUrlGenerator,
UrlMode urlMode = UrlMode.Default) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode);
/// <summary>
/// Gets the underlying image processing service URL from the IPublishedContent item.
@@ -105,6 +113,7 @@ namespace Umbraco.Extensions
/// <example><![CDATA[
/// furtherOptions: "bgcolor=fff"
/// ]]></example></param>
/// <param name="urlMode">The url mode.</param>
/// <returns>
/// The URL of the cropped image.
/// </returns>
@@ -123,7 +132,8 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions);
string furtherOptions = null,
UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, urlMode);
public static string GetCropUrl(
this MediaWithCrops mediaWithCrops,
@@ -140,14 +150,15 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null)
string furtherOptions = null,
UrlMode urlMode = UrlMode.Default)
{
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);
return mediaWithCrops.Content.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, mediaWithCrops.LocalCrops, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, urlMode);
}
private static string GetCropUrl(
@@ -167,7 +178,8 @@ namespace Umbraco.Extensions
bool preferFocalPoint = false,
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null)
string furtherOptions = null,
UrlMode urlMode = UrlMode.Default)
{
if (mediaItem == null)
{
@@ -179,7 +191,7 @@ namespace Umbraco.Extensions
return null;
}
var mediaItemUrl = mediaItem.MediaUrl(publishedUrlProvider, propertyAlias: propertyAlias);
var mediaItemUrl = mediaItem.MediaUrl(publishedUrlProvider, propertyAlias: propertyAlias, mode: urlMode);
// Only get crops from media when required and used
if (localCropsOnly == false && (imageCropMode == ImageCropMode.Crop || imageCropMode == null))

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
@@ -209,27 +208,27 @@ namespace Umbraco.Extensions
return $"{version}.{runtimeMinifier.CacheBuster}".GenerateHash();
}
public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, IPublishedContent mediaItem, string cropAlias, bool htmlEncode = true)
public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, IPublishedContent mediaItem, string cropAlias, bool htmlEncode = true, UrlMode urlMode = UrlMode.Default)
{
if (mediaItem == null)
{
return HtmlString.Empty;
}
var url = mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true);
var url = mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode);
return CreateHtmlString(url, htmlEncode);
}
private static IHtmlContent CreateHtmlString(string url, bool htmlEncode) => htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url);
public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias, bool htmlEncode = true)
public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias, bool htmlEncode = true, UrlMode urlMode = UrlMode.Default)
{
if (mediaItem == null)
{
return HtmlString.Empty;
}
var url = mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true);
var url = mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode);
return CreateHtmlString(url, htmlEncode);
}
@@ -246,7 +245,8 @@ namespace Umbraco.Extensions
bool useCropDimensions = false,
bool cacheBuster = true,
string furtherOptions = null,
bool htmlEncode = true)
bool htmlEncode = true,
UrlMode urlMode = UrlMode.Default)
{
if (mediaItem == null)
{
@@ -254,7 +254,7 @@ namespace Umbraco.Extensions
}
var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions);
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, urlMode);
return CreateHtmlString(url, htmlEncode);
}

View File

@@ -3,7 +3,7 @@
* @name umbraco.directives.directive:umbTree
* @restrict E
**/
function umbTreeDirective($q, treeService, navigationService, notificationsService) {
function umbTreeDirective($q, treeService, notificationsService) {
return {
restrict: 'E',
@@ -357,8 +357,6 @@ function umbTreeDirective($q, treeService, navigationService, notificationsServi
defined on the tree
*/
$scope.select = function (n, ev) {
navigationService.hideMenu();
if (n.metaData && n.metaData.noAccess === true) {
ev.preventDefault();

View File

@@ -127,7 +127,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
var aboveClass = "above-backdrop";
var leftColumn = document.getElementById("leftcolumn");
if(leftColumn) {
if (leftColumn) {
var isLeftColumnOnTop = leftColumn.classList.contains(aboveClass);
if (isLeftColumnOnTop) {

View File

@@ -585,7 +585,6 @@
display: inline-block;
position: relative;
vertical-align: top;
flex:0;
}
.umb-fileupload,

View File

@@ -2044,4 +2044,8 @@ Mange hilsner fra Umbraco robotten
<key alias="FileWriting">Filskrivning</key>
<key alias="MediaFolderCreation">Medie mappeoprettelse</key>
</area>
<area alias="treeSearch">
<key alias="searchResult">resultat</key>
<key alias="searchResults">resultater</key>
</area>
</language>