diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index c62ad1c494..0f5a12b34b 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -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
\ No newline at end of file
+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
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
index 17312c6bd8..e88327b779 100644
--- a/.devcontainer/docker-compose.yml
+++ b/.devcontainer/docker-compose.yml
@@ -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
diff --git a/.gitattributes b/.gitattributes
index 3241b6511c..8ac58cf5fe 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -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
diff --git a/build/NuSpecs/tools/Web.config.cloud.xdt b/build/NuSpecs/tools/Web.config.cloud.xdt
new file mode 100644
index 0000000000..988c741126
--- /dev/null
+++ b/build/NuSpecs/tools/Web.config.cloud.xdt
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/build.ps1 b/build/build.ps1
index ea0a50e5b1..ff24079532 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -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" )
diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/build/templates/UmbracoProject/UmbracoProject.csproj
index 09e95b43b0..d1f40a2740 100644
--- a/build/templates/UmbracoProject/UmbracoProject.csproj
+++ b/build/templates/UmbracoProject/UmbracoProject.csproj
@@ -1,7 +1,11 @@
+
net5.0
Umbraco.Cms.Web.UI
+ $(DefaultItemExcludes);App_Plugins/**;
+ $(DefaultItemExcludes);umbraco/**;
+ $(DefaultItemExcludes);wwwroot/media/**;
@@ -10,42 +14,18 @@
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- Always
-
-
- true
- Always
-
-
- true
- Always
-
-
-
-
+
+
+
+
+
@@ -53,4 +33,5 @@
false
false
+
diff --git a/src/Umbraco.Core/Cache/IValueEditorCache.cs b/src/Umbraco.Core/Cache/IValueEditorCache.cs
new file mode 100644
index 0000000000..f283d730b5
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IValueEditorCache.cs
@@ -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 dataTypeIds);
+ }
+}
diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs
new file mode 100644
index 0000000000..44aa83d44d
--- /dev/null
+++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs
@@ -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> _valueEditorCache;
+ private readonly object _dictionaryLocker;
+
+ public ValueEditorCache()
+ {
+ _valueEditorCache = new Dictionary>();
+ _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 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 { [dataType.Id] = valueEditor };
+ return valueEditor;
+ }
+ }
+
+ public void ClearCache(IEnumerable 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 editors in _valueEditorCache.Values)
+ {
+ editors.Remove(id);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs
new file mode 100644
index 0000000000..c815ca7a71
--- /dev/null
+++ b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs
@@ -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
+ {
+ 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 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();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
index 648af8f082..6f6a53df66 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
@@ -253,6 +253,9 @@ namespace Umbraco.Cms.Core.DependencyInjection
// register a basic/noop published snapshot service to be replaced
Services.AddSingleton();
+
+ // Register ValueEditorCache used for validation
+ Services.AddSingleton();
}
}
}
diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
index 8c35e80988..db10cae416 100644
--- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs
+++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
@@ -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;
}
diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs
deleted file mode 100644
index 64199a1c51..0000000000
--- a/src/Umbraco.Core/IO/SystemFiles.cs
+++ /dev/null
@@ -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");
- }
- }
-}
diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs
index f5b3574be7..7a0264caf3 100644
--- a/src/Umbraco.Core/Models/Media.cs
+++ b/src/Umbraco.Core/Models/Media.cs
@@ -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
///
/// name of the Media object
/// Parent object
- /// MediaType for the current Media object
- public Media(string name, IMedia parent, IMediaType contentType)
- : this(name, parent, contentType, new PropertyCollection())
+ /// MediaType for the current Media object
+ public Media(string name, IMedia parent, IMediaType mediaType)
+ : this(name, parent, mediaType, new PropertyCollection())
{ }
///
@@ -25,10 +25,10 @@ namespace Umbraco.Cms.Core.Models
///
/// name of the Media object
/// Parent object
- /// MediaType for the current Media object
+ /// MediaType for the current Media object
/// Collection of properties
- 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)
{ }
///
@@ -36,9 +36,9 @@ namespace Umbraco.Cms.Core.Models
///
/// name of the Media object
/// Id of the Parent IMedia
- /// MediaType for the current Media object
- public Media(string name, int parentId, IMediaType contentType)
- : this(name, parentId, contentType, new PropertyCollection())
+ /// MediaType for the current Media object
+ public Media(string name, int parentId, IMediaType mediaType)
+ : this(name, parentId, mediaType, new PropertyCollection())
{ }
///
@@ -46,10 +46,10 @@ namespace Umbraco.Cms.Core.Models
///
/// Name of the Media object
/// Id of the Parent IMedia
- /// MediaType for the current Media object
+ /// MediaType for the current Media object
/// Collection of properties
- 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)
{ }
///
@@ -57,9 +57,9 @@ namespace Umbraco.Cms.Core.Models
///
/// New MediaType for this Media
/// Leaves PropertyTypes intact after change
- internal void ChangeContentType(IMediaType contentType)
+ internal void ChangeContentType(IMediaType mediaType)
{
- ChangeContentType(contentType, false);
+ ChangeContentType(mediaType, false);
}
///
@@ -68,14 +68,14 @@ namespace Umbraco.Cms.Core.Models
///
/// New MediaType for this Media
/// Boolean indicating whether to clear PropertyTypes upon change
- 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;
diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
index c8b9f3a51f..e2fc0071be 100644
--- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
@@ -21,7 +21,6 @@ namespace Umbraco.Cms.Core.PropertyEditors
public class DataEditor : IDataEditor
{
private IDictionary _defaultConfiguration;
- private IDataValueEditor _reusableEditor;
///
/// Initializes a new instance of the class.
@@ -90,8 +89,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
/// simple enough for now.
///
// TODO: point of that one? shouldn't we always configure?
- public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_reusableEditor ?? (_reusableEditor = CreateValueEditor()));
-
+ public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor();
///
///
diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs
index 35845f3cd0..6e6f549b03 100644
--- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs
+++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs
@@ -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
diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs
index a0ba0ff128..ceac767a8c 100644
--- a/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs
+++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs
@@ -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 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)
diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
index 40c250f86e..644b2c278f 100644
--- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
@@ -33,7 +33,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
///
/// Initializes a new instance of the class.
///
- /// Accessor for the current request.
+ /// The current hosting environment
/// Representation of the main application domain.
/// The configuration for keep alive settings.
/// The typed logger.
@@ -86,7 +86,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
using (_profilingLogger.DebugDuration("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.");
diff --git a/src/Umbraco.Infrastructure/Persistence/LocalDb.cs b/src/Umbraco.Infrastructure/Persistence/LocalDb.cs
index 0ca98d2b14..0dbe84b07a 100644
--- a/src/Umbraco.Infrastructure/Persistence/LocalDb.cs
+++ b/src/Umbraco.Infrastructure/Persistence/LocalDb.cs
@@ -927,6 +927,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
return p.ExitCode;
}
+
}
///
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs
index 24c9b37cde..9a7ecdbf29 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs
@@ -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 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;
diff --git a/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs b/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs
index 5fd7971976..70580153de 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs
@@ -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;
}
///
@@ -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().
diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs
index 60441a013d..d10e269fad 100644
--- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs
+++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs
@@ -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();
+ private IValueEditorCache ValueEditorCache => GetRequiredService();
+
[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);
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ValueEditorCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ValueEditorCacheTests.cs
new file mode 100644
index 0000000000..94cd9924f9
--- /dev/null
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ValueEditorCacheTests.cs
@@ -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 CreateDataTypeMock(int id)
+ {
+ var mock = new Mock();
+ mock.Setup(x => x.Id).Returns(id);
+ return mock;
+ }
+
+ ///
+ /// A fake IDataEditor
+ ///
+ ///
+ /// This is necessary to ensure that different objects are returned from GetValueEditor
+ ///
+ 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();
+ }
+ public IDataValueEditor GetValueEditor(object configuration) => GetValueEditor();
+
+ public IDictionary DefaultConfiguration { get; }
+ public IConfigurationEditor GetConfigurationEditor() => throw new System.NotImplementedException();
+
+ public IPropertyIndexValueFactory PropertyIndexValueFactory { get; }
+
+ public int ValueEditorCount;
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs
index 892ef696c3..c2d5b785c9 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs
@@ -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());
}
}
}
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs
index 590ff58222..b76719888f 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs
@@ -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 s_logger = Mock.Of>();
+
+
+
[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(() => 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
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs
index bfbe6258cc..942ba8cf4a 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs
@@ -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());
+ validationService = new PropertyValidationService(propEditors, dataTypeService.Object, Mock.Of(), new ValueEditorCache());
}
[Test]
diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs
index 38f6f47235..22ddc15511 100644
--- a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs
@@ -22,16 +22,18 @@ namespace Umbraco.Extensions
///
/// The IPublishedContent item.
/// The crop alias e.g. thumbnail.
+ /// The url mode.
///
/// The URL of the cropped image.
///
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);
///
/// Gets the crop URL by using only the specified .
@@ -39,14 +41,16 @@ namespace Umbraco.Extensions
/// The media item.
/// The image cropper value.
/// The crop alias.
+ /// The url mode.
///
/// The image crop URL.
///
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);
///
/// 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
/// The IPublishedContent item.
/// The property alias of the property containing the JSON data e.g. umbracoFile.
/// The crop alias e.g. thumbnail.
+ /// The url mode.
///
/// The URL of the cropped image.
///
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);
///
/// Gets the underlying image processing service URL from the IPublishedContent item.
@@ -84,6 +90,7 @@ namespace Umbraco.Extensions
///
+ /// The url mode.
///
/// The URL of the cropped image.
///
@@ -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
);
///
@@ -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,
diff --git a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs
index ae367b1cf9..a3e96ebebb 100644
--- a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs
@@ -20,6 +20,7 @@ namespace Umbraco.Extensions
/// The image URL generator.
/// The published value fallback.
/// The published URL provider.
+ /// The url mode.
///
/// The URL of the cropped image.
///
@@ -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);
///
/// Gets the crop URL by using only the specified .
@@ -46,6 +49,7 @@ namespace Umbraco.Extensions
/// The image URL generator.
/// The published value fallback.
/// The published URL provider.
+ /// The url mode.s
///
/// The image crop URL.
///
@@ -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);
///
/// 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
/// The image URL generator.
/// The published value fallback.
/// The published URL provider.
+ /// The url mode.
///
/// The URL of the cropped image.
///
@@ -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);
///
/// Gets the underlying image processing service URL from the IPublishedContent item.
@@ -105,6 +113,7 @@ namespace Umbraco.Extensions
///
+ /// The url mode.
///
/// The URL of the cropped image.
///
@@ -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))
diff --git a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs
index aefd102725..ee45db39c9 100644
--- a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs
@@ -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);
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js
index 358223901e..a39475f4da 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js
@@ -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();
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js
index 88894ac47a..b6609a73fc 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js
@@ -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) {
diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
index 87603671dd..b735b6d7e4 100644
--- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less
+++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
@@ -585,7 +585,6 @@
display: inline-block;
position: relative;
vertical-align: top;
- flex:0;
}
.umb-fileupload,
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
index 9f7ea3c822..cbd3457b65 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
@@ -2044,4 +2044,8 @@ Mange hilsner fra Umbraco robotten
Filskrivning
Medie mappeoprettelse
+
+ resultat
+ resultater
+