Merge remote-tracking branch 'origin/v8/dev' into v9/dev

# Conflicts:
#	src/Umbraco.Core/Composing/CompositionExtensions/Services.cs
#	src/Umbraco.Core/Constants-AppSettings.cs
#	src/Umbraco.Core/Dashboards/ContentDashboardSettings.cs
#	src/Umbraco.Core/Dashboards/IContentDashboardSettings.cs
#	src/Umbraco.Core/Manifest/ManifestParser.cs
#	src/Umbraco.Core/Manifest/PackageManifest.cs
#	src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
#	src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
#	src/Umbraco.Core/Models/PropertyTagsExtensions.cs
#	src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs
#	src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs
#	src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
#	src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs
#	src/Umbraco.Core/Serialization/JsonToStringConverter.cs
#	src/Umbraco.Core/Sync/ApplicationUrlHelper.cs
#	src/Umbraco.Core/Telemetry/ITelemetryService.cs
#	src/Umbraco.Core/Telemetry/Models/PackageTelemetry.cs
#	src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs
#	src/Umbraco.Core/Telemetry/TelemetryService.cs
#	src/Umbraco.Tests/Manifest/ManifestParserTests.cs
#	src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs
#	src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs
#	src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs
#	src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs
#	src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs
#	src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
#	src/Umbraco.Web.UI/Umbraco/config/lang/da.xml
#	src/Umbraco.Web.UI/Umbraco/config/lang/en.xml
#	src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml
#	src/Umbraco.Web.UI/web.Template.config
#	src/Umbraco.Web/BatchedDatabaseServerMessenger.cs
#	src/Umbraco.Web/Cache/DistributedCacheBinder.cs
#	src/Umbraco.Web/Compose/BlockEditorComponent.cs
#	src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs
#	src/Umbraco.Web/Editors/AuthenticationController.cs
#	src/Umbraco.Web/Editors/ContentTypeController.cs
#	src/Umbraco.Web/Editors/DashboardController.cs
#	src/Umbraco.Web/Editors/EntityController.cs
#	src/Umbraco.Web/Editors/UsersController.cs
#	src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs
#	src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs
#	src/Umbraco.Web/PropertyEditors/MediaPicker3PropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs
#	src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/MultipleValueEditor.cs
#	src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs
#	src/Umbraco.Web/Telemetry/ReportSiteTask.cs
#	src/Umbraco.Web/Telemetry/TelemetryComponent.cs
#	src/Umbraco.Web/Trees/ContentTreeController.cs
#	src/Umbraco.Web/Umbraco.Web.csproj
#	src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs
This commit is contained in:
Bjarke Berg
2022-01-21 12:40:18 +01:00
27 changed files with 385 additions and 111 deletions

View File

@@ -68,12 +68,15 @@ namespace Umbraco.Extensions
switch (storageType)
{
case TagsStorageType.Csv:
property.SetValue(string.Join(delimiter.ToString(), currentTags.Union(trimmedTags)), culture); // csv string
property.SetValue(string.Join(delimiter.ToString(), currentTags.Union(trimmedTags)).NullOrWhiteSpaceAsNull(), culture); // csv string
break;
case TagsStorageType.Json:
var updatedTags = currentTags.Union(trimmedTags).ToArray();
var updatedValue = updatedTags.Length == 0 ? null : serializer.Serialize(updatedTags);
property.SetValue(updatedValue, culture); // json array
break;
property.SetValue(serializer.Serialize(currentTags.Union(trimmedTags).ToArray()), culture); // json array
break;
}
}
else
@@ -81,7 +84,7 @@ namespace Umbraco.Extensions
switch (storageType)
{
case TagsStorageType.Csv:
property.SetValue(string.Join(delimiter.ToString(), trimmedTags), culture); // csv string
property.SetValue(string.Join(delimiter.ToString(), trimmedTags).NullOrWhiteSpaceAsNull(), culture); // csv string
break;
case TagsStorageType.Json:
@@ -124,11 +127,13 @@ namespace Umbraco.Extensions
switch (storageType)
{
case TagsStorageType.Csv:
property.SetValue(string.Join(delimiter.ToString(), currentTags.Except(trimmedTags)), culture); // csv string
property.SetValue(string.Join(delimiter.ToString(), currentTags.Except(trimmedTags)).NullOrWhiteSpaceAsNull(), culture); // csv string
break;
case TagsStorageType.Json:
property.SetValue(serializer.Serialize(currentTags.Except(trimmedTags).ToArray()), culture); // json array
var updatedTags = currentTags.Except(trimmedTags).ToArray();
var updatedValue = updatedTags.Length == 0 ? null : serializer.Serialize(updatedTags);
property.SetValue(updatedValue, culture); // json array
break;
}
}
@@ -160,7 +165,7 @@ namespace Umbraco.Extensions
case TagsStorageType.Json:
try
{
return serializer.Deserialize<string[]>(value).Select(x => x.ToString().Trim());
return serializer.Deserialize<string[]>(value).Select(x => x.Trim());
}
catch (Exception)
{

View File

@@ -197,6 +197,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
default:
throw new ArgumentOutOfRangeException();
}
return value.TryConvertTo(valueType);
}
@@ -232,6 +233,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
StaticApplicationLogging.Logger.LogWarning("The value {EditorValue} cannot be converted to the type {StorageTypeValue}", editorValue.Value, ValueTypes.ToStorageType(ValueType));
return null;
}
return result.Result;
}

View File

@@ -238,7 +238,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
MapBlockItemData(blockEditorData.BlockValue.SettingsData);
// return json
return JsonConvert.SerializeObject(blockEditorData.BlockValue);
return JsonConvert.SerializeObject(blockEditorData.BlockValue, Formatting.None);
}
#endregion

View File

@@ -52,7 +52,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
UpdateBlockListRecursively(blockListValue, createGuid);
return JsonConvert.SerializeObject(blockListValue.BlockValue);
return JsonConvert.SerializeObject(blockListValue.BlockValue, Formatting.None);
}
private void UpdateBlockListRecursively(BlockEditorData blockListData, Func<Guid> createGuid)

View File

@@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Serialization;

View File

@@ -2,6 +2,8 @@
// See LICENSE for more details.
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
@@ -41,11 +43,13 @@ namespace Umbraco.Cms.Core.PropertyEditors
foreach (var cultureVal in propVals)
{
// Remove keys from published value & any nested properties
var updatedPublishedVal = FormatPropertyValue(cultureVal.PublishedValue?.ToString(), onlyMissingKeys);
var publishedValue = cultureVal.PublishedValue is JToken jsonPublishedValue ? jsonPublishedValue.ToString(Formatting.None) : cultureVal.PublishedValue?.ToString();
var updatedPublishedVal = FormatPropertyValue(publishedValue, onlyMissingKeys).NullOrWhiteSpaceAsNull();
cultureVal.PublishedValue = updatedPublishedVal;
// Remove keys from edited/draft value & any nested properties
var updatedEditedVal = FormatPropertyValue(cultureVal.EditedValue?.ToString(), onlyMissingKeys);
var editedValue = cultureVal.EditedValue is JToken jsonEditedValue ? jsonEditedValue.ToString(Formatting.None) : cultureVal.EditedValue?.ToString();
var updatedEditedVal = FormatPropertyValue(editedValue, onlyMissingKeys).NullOrWhiteSpaceAsNull();
cultureVal.EditedValue = updatedEditedVal;
}
}

View File

@@ -139,7 +139,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
}
// Convert back to raw JSON for persisting
return JsonConvert.SerializeObject(grid);
return JsonConvert.SerializeObject(grid, Formatting.None);
}
/// <summary>

View File

@@ -211,7 +211,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
var sourcePath = _mediaFileManager.FileSystem.GetRelativePath(src);
var copyPath = _mediaFileManager.CopyFile(notification.Copy, property.PropertyType, sourcePath);
jo["src"] = _mediaFileManager.FileSystem.GetUrl(copyPath);
notification.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.Culture, propertyValue.Segment);
notification.Copy.SetValue(property.Alias, jo.ToString(Formatting.None), propertyValue.Culture, propertyValue.Segment);
isUpdated = true;
}
}
@@ -272,17 +272,11 @@ namespace Umbraco.Cms.Core.PropertyEditors
// it can happen when an image is uploaded via the folder browser, in which case
// the property value will be the file source eg '/media/23454/hello.jpg' and we
// are fixing that anomaly here - does not make any sense at all but... bah...
var dt = _dataTypeService.GetDataType(property.PropertyType.DataTypeId);
var config = dt?.ConfigurationAs<ImageCropperConfiguration>();
src = svalue;
var json = new
property.SetValue(JsonConvert.SerializeObject(new
{
src = svalue,
crops = config == null ? Array.Empty<ImageCropperConfiguration.Crop>() : config.Crops
};
property.SetValue(JsonConvert.SerializeObject(json), pvalue.Culture, pvalue.Segment);
src = svalue
}, Formatting.None), pvalue.Culture, pvalue.Segment);
}
else
{

View File

@@ -205,6 +205,10 @@ namespace Umbraco.Cms.Core.PropertyEditors
{
src = val,
crops = crops
},new JsonSerializerSettings()
{
Formatting = Formatting.None,
NullValueHandling = NullValueHandling.Ignore
});
}
}

View File

@@ -12,6 +12,7 @@ using Newtonsoft.Json;
using System;
using System.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json.Linq;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors
@@ -53,23 +54,55 @@ namespace Umbraco.Cms.Core.PropertyEditors
internal class MediaPicker3PropertyValueEditor : DataValueEditor, IDataValueReference
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IDataTypeService _dataTypeService;
public MediaPicker3PropertyValueEditor(
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
DataEditorAttribute attribute)
DataEditorAttribute attribute,
IDataTypeService dataTypeService)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_jsonSerializer = jsonSerializer;
_dataTypeService = dataTypeService;
}
public override object ToEditor(IProperty property, string culture = null, string segment = null)
{
var value = property.GetValue(culture, segment);
return Deserialize(_jsonSerializer, value);
var dtos = Deserialize(_jsonSerializer, value).ToList();
var dataType = _dataTypeService.GetDataType(property.PropertyType.DataTypeId);
if (dataType?.Configuration != null)
{
var configuration = dataType.ConfigurationAs<MediaPicker3Configuration>();
foreach (var dto in dtos)
{
dto.ApplyConfiguration(configuration);
}
}
return dtos;
}
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
{
if (editorValue.Value is JArray dtos)
{
// Clean up redundant/default data
foreach (var dto in dtos.Values<JObject>())
{
MediaWithCropsDto.Prune(dto);
}
return dtos.ToString(Formatting.None);
}
return base.FromEditor(editorValue, currentValue);
}
///<remarks>
@@ -142,6 +175,52 @@ namespace Umbraco.Cms.Core.PropertyEditors
[DataMember(Name = "focalPoint")]
public ImageCropperValue.ImageCropperFocalPoint FocalPoint { get; set; }
/// <summary>
/// Applies the configuration to ensure only valid crops are kept and have the correct width/height.
/// </summary>
/// <param name="configuration">The configuration.</param>
public void ApplyConfiguration(MediaPicker3Configuration configuration)
{
var crops = new List<ImageCropperValue.ImageCropperCrop>();
var configuredCrops = configuration?.Crops;
if (configuredCrops != null)
{
foreach (var configuredCrop in configuredCrops)
{
var crop = Crops?.FirstOrDefault(x => x.Alias == configuredCrop.Alias);
crops.Add(new ImageCropperValue.ImageCropperCrop
{
Alias = configuredCrop.Alias,
Width = configuredCrop.Width,
Height = configuredCrop.Height,
Coordinates = crop?.Coordinates
});
}
}
Crops = crops;
if (configuration?.EnableLocalFocalPoint == false)
{
FocalPoint = null;
}
}
/// <summary>
/// Removes redundant crop data/default focal point.
/// </summary>
/// <param name="value">The media with crops DTO.</param>
/// <returns>
/// The cleaned up value.
/// </returns>
/// <remarks>
/// Because the DTO uses the same JSON keys as the image cropper value for crops and focal point, we can re-use the prune method.
/// </remarks>
public static void Prune(JObject value) => ImageCropperValue.Prune(value);
}
}
}

View File

@@ -141,6 +141,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
private static readonly JsonSerializerSettings LinkDisplayJsonSerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.None,
NullValueHandling = NullValueHandling.Ignore
};
@@ -150,13 +151,17 @@ namespace Umbraco.Cms.Core.PropertyEditors
if (string.IsNullOrEmpty(value))
{
return string.Empty;
return null;
}
try
{
var links = JsonConvert.DeserializeObject<List<LinkDisplay>>(value);
if (links.Count == 0)
return null;
return JsonConvert.SerializeObject(
from link in JsonConvert.DeserializeObject<List<LinkDisplay>>(value)
from link in links
select new MultiUrlPickerValueEditor.LinkDto
{
Name = link.Name,
@@ -164,8 +169,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
Target = link.Target,
Udi = link.Udi,
Url = link.Udi == null ? link.Url : null, // only save the URL for external links
}, LinkDisplayJsonSerializerSettings
);
},
LinkDisplayJsonSerializerSettings);
}
catch (Exception ex)
{

View File

@@ -80,7 +80,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
{
var asArray = editorValue.Value as JArray;
if (asArray == null)
if (asArray == null || asArray.HasValues == false)
{
return null;
}

View File

@@ -59,14 +59,19 @@ namespace Umbraco.Cms.Core.PropertyEditors
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
{
var json = editorValue.Value as JArray;
if (json == null)
if (json == null || json.HasValues == false)
{
return null;
}
var values = json.Select(item => item.Value<string>()).ToArray();
return JsonConvert.SerializeObject(values);
if (values.Length == 0)
{
return null;
}
return JsonConvert.SerializeObject(values, Formatting.None);
}
}
}

View File

@@ -105,7 +105,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
var rows = _nestedContentValues.GetPropertyValues(propertyValue);
if (rows.Count == 0)
return string.Empty;
return null;
foreach (var row in rows.ToList())
{
@@ -136,7 +136,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
}
}
return JsonConvert.SerializeObject(rows).ToXmlString<string>();
return JsonConvert.SerializeObject(rows, Formatting.None).ToXmlString<string>();
}
#endregion
@@ -231,7 +231,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
var rows = _nestedContentValues.GetPropertyValues(editorValue.Value);
if (rows.Count == 0)
return string.Empty;
return null;
foreach (var row in rows.ToList())
{
@@ -256,7 +256,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
}
// return json
return JsonConvert.SerializeObject(rows);
return JsonConvert.SerializeObject(rows, Formatting.None);
}
#endregion

View File

@@ -3,6 +3,7 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Extensions;
@@ -32,7 +33,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
UpdateNestedContentKeysRecursively(complexEditorValue, onlyMissingKeys, createGuid);
return complexEditorValue.ToString();
return complexEditorValue.ToString(Formatting.None);
}
private void UpdateNestedContentKeysRecursively(JToken json, bool onlyMissingKeys, Func<Guid> createGuid)
@@ -65,7 +66,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
var parsed = JToken.Parse(propVal);
UpdateNestedContentKeysRecursively(parsed, onlyMissingKeys, createGuid);
// set the value to the updated one
prop.Value = parsed.ToString();
prop.Value = parsed.ToString(Formatting.None);
}
}
}

View File

@@ -157,7 +157,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages);
var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved);
return parsed;
return parsed.NullOrWhiteSpaceAsNull();
}
/// <summary>

View File

@@ -73,7 +73,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
if (editorValue.Value is JArray json)
{
return json.Select(x => x.Value<string>());
return json.HasValues ? json.Select(x => x.Value<string>()) : null;
}
if (string.IsNullOrWhiteSpace(value) == false)

View File

@@ -7,6 +7,7 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Strings;
@@ -122,13 +123,19 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
/// </summary>
/// <returns></returns>
public bool HasFocalPoint()
=> FocalPoint != null && (FocalPoint.Left != 0.5m || FocalPoint.Top != 0.5m);
=> FocalPoint is ImageCropperFocalPoint focalPoint && (focalPoint.Left != 0.5m || focalPoint.Top != 0.5m);
/// <summary>
/// Determines whether the value has crops.
/// </summary>
public bool HasCrops()
=> Crops is IEnumerable<ImageCropperCrop> crops && crops.Any();
/// <summary>
/// Determines whether the value has a specified crop.
/// </summary>
public bool HasCrop(string alias)
=> Crops != null && Crops.Any(x => x.Alias == alias);
=> Crops is IEnumerable<ImageCropperCrop> crops && crops.Any(x => x.Alias == alias);
/// <summary>
/// Determines whether the value has a source image.
@@ -167,6 +174,51 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
};
}
/// <summary>
/// Removes redundant crop data/default focal point.
/// </summary>
/// <param name="value">The image cropper value.</param>
/// <returns>
/// The cleaned up value.
/// </returns>
public static void Prune(JObject value)
{
if (value is null) throw new ArgumentNullException(nameof(value));
if (value.TryGetValue("crops", out var crops))
{
if (crops.HasValues)
{
foreach (var crop in crops.Values<JObject>().ToList())
{
if (crop.TryGetValue("coordinates", out var coordinates) == false || coordinates.HasValues == false)
{
// Remove crop without coordinates
crop.Remove();
continue;
}
// Width/height are already stored in the crop configuration
crop.Remove("width");
crop.Remove("height");
}
}
if (crops.HasValues == false)
{
// Remove empty crops
value.Remove("crops");
}
}
if (value.TryGetValue("focalPoint", out var focalPoint) &&
(focalPoint.HasValues == false || (focalPoint.Value<decimal>("top") == 0.5m && focalPoint.Value<decimal>("left") == 0.5m)))
{
// Remove empty/default focal point
value.Remove("focalPoint");
}
}
#region IEquatable
/// <inheritdoc />

View File

@@ -12,6 +12,8 @@ namespace Umbraco.Cms.Infrastructure.Serialization
{
JsonSerializerSettings.Converters.Add(new FuzzyBooleanConverter());
JsonSerializerSettings.ContractResolver = new ConfigurationCustomContractResolver();
JsonSerializerSettings.Formatting = Formatting.None;
JsonSerializerSettings.NullValueHandling = NullValueHandling.Ignore;
}
private class ConfigurationCustomContractResolver : DefaultContractResolver

View File

@@ -14,7 +14,8 @@ namespace Umbraco.Cms.Infrastructure.Serialization
Converters = new List<JsonConverter>()
{
new StringEnumConverter()
}
},
Formatting = Formatting.None
};
public string Serialize(object input)
{

View File

@@ -22,7 +22,7 @@ namespace Umbraco.Cms.Infrastructure.Serialization
}
// Load JObject from stream
JObject jObject = JObject.Load(reader);
return jObject.ToString();
return jObject.ToString(Formatting.None);
}
public override bool CanConvert(Type objectType)

View File

@@ -678,7 +678,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
r = code
});
// Construct full URL using configured application URL (which will fall back to request)
// Construct full URL using configured application URL (which will fall back to current request)
Uri applicationUri = _httpContextAccessor.GetRequiredHttpContext().Request.GetApplicationUri(_webRoutingSettings);
var callbackUri = new Uri(applicationUri, action);
return callbackUri.ToString();

View File

@@ -282,7 +282,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
if (_emailSender.CanSendRequiredEmail())
{
AddActionNode<ActionNotify>(item, menu, true, opensDialog: true);
menu.Items.Add(new MenuItem("notify", LocalizedTextService)
{
Icon = "megaphone",
SeparatorBefore = true,
OpensDialog = true
});
}
if((item is DocumentEntitySlim documentEntity && documentEntity.IsContainer) == false)

View File

@@ -152,12 +152,12 @@ function entityResource($q, $http, umbRequestHelper) {
$http.post(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetUrlsByUdis",
"GetUrlsByIds",
query),
{
udis: udis
ids: ids
}),
'Failed to retrieve url map for udis ' + udis);
'Failed to retrieve url map for ids ' + ids);
},
getUrlByUdi: function (udi, culture) {

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Web.HealthCheck.Checks.Config;
namespace Umbraco.Web.HealthCheck.Checks.Security
{
[HealthCheck(
"6708CA45-E96E-40B8-A40A-0607C1CA7F28",
"Application URL Configuration",
Description = "Checks if the Umbraco application URL is configured for your site.",
Group = "Security")]
public class UmbracoApplicationUrlCheck : HealthCheck
{
private readonly ILocalizedTextService _textService;
private readonly IRuntimeState _runtime;
private readonly IUmbracoSettingsSection _settings;
private const string SetApplicationUrlAction = "setApplicationUrl";
public UmbracoApplicationUrlCheck(ILocalizedTextService textService, IRuntimeState runtime, IUmbracoSettingsSection settings)
{
_textService = textService;
_runtime = runtime;
_settings = settings;
}
/// <summary>
/// Executes the action and returns its status
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
{
switch (action.Alias)
{
case SetApplicationUrlAction:
return SetUmbracoApplicationUrl();
default:
throw new InvalidOperationException("UmbracoApplicationUrlCheck action requested is either not executable or does not exist");
}
}
public override IEnumerable<HealthCheckStatus> GetStatus()
{
//return the statuses
return new[] { CheckUmbracoApplicationUrl() };
}
private HealthCheckStatus CheckUmbracoApplicationUrl()
{
var url = _settings.WebRouting.UmbracoApplicationUrl;
string resultMessage;
StatusResultType resultType;
var actions = new List<HealthCheckAction>();
if (url.IsNullOrWhiteSpace())
{
resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultFalse");
resultType = StatusResultType.Warning;
actions.Add(new HealthCheckAction(SetApplicationUrlAction, Id)
{
Name = _textService.Localize("healthcheck", "umbracoApplicationUrlConfigureButton"),
Description = _textService.Localize("healthcheck", "umbracoApplicationUrlConfigureDescription")
});
}
else
{
resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultTrue", new[] { url });
resultType = StatusResultType.Success;
}
return new HealthCheckStatus(resultMessage)
{
ResultType = resultType,
Actions = actions
};
}
private HealthCheckStatus SetUmbracoApplicationUrl()
{
var configFilePath = IOHelper.MapPath("~/config/umbracoSettings.config");
const string xPath = "/settings/web.routing/@umbracoApplicationUrl";
var configurationService = new ConfigurationService(configFilePath, xPath, _textService);
var urlValue = _runtime.ApplicationUrl.ToString();
var updateConfigFile = configurationService.UpdateConfigFile(urlValue);
if (updateConfigFile.Success)
{
return
new HealthCheckStatus(_textService.Localize("healthcheck", "umbracoApplicationUrlConfigureSuccess", new[] { urlValue }))
{
ResultType = StatusResultType.Success
};
}
return
new HealthCheckStatus(_textService.Localize("healthcheck", "umbracoApplicationUrlConfigureError", new[] { updateConfigFile.Result }))
{
ResultType = StatusResultType.Error
};
}
}
}

View File

@@ -3,6 +3,7 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.PropertyEditors;
@@ -11,6 +12,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
[TestFixture]
public class NestedContentPropertyComponentTests
{
private static void AreEqualJson(string expected, string actual)
{
Assert.AreEqual(JToken.Parse(expected), JToken.Parse(actual));
}
[Test]
public void Invalid_Json()
{
@@ -27,17 +33,17 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
Guid GuidFactory() => guids[guidCounter++];
var json = @"[
{""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1"",""ncContentTypeAlias"":""nested"",""text"":""woot""},
{""key"":""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",""name"":""Item 2"",""ncContentTypeAlias"":""nested"",""text"":""zoot""}
]";
{""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1"",""ncContentTypeAlias"":""nested"",""text"":""woot""},
{""key"":""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",""name"":""Item 2"",""ncContentTypeAlias"":""nested"",""text"":""zoot""}
]";
var expected = json
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
.Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString());
var component = new NestedContentPropertyHandler();
var result = component.CreateNestedContentKeys(json, false, GuidFactory);
var actual = component.CreateNestedContentKeys(json, false, GuidFactory);
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
AreEqualJson(expected, actual);
}
[Test]
@@ -48,29 +54,27 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
Guid GuidFactory() => guids[guidCounter++];
var json = @"[{
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
""text"": ""woot""
}, {
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
""name"": ""Item 2"",
""ncContentTypeAlias"": ""list"",
""text"": ""zoot"",
""subItems"": [{
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
""text"": ""woot""
}, {
""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"",
""name"": ""Item 2"",
""ncContentTypeAlias"": ""text"",
""text"": ""zoot""
}
]
}
]";
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
""text"": ""woot""
}, {
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
""name"": ""Item 2"",
""ncContentTypeAlias"": ""list"",
""text"": ""zoot"",
""subItems"": [{
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
""text"": ""woot""
}, {
""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"",
""name"": ""Item 2"",
""ncContentTypeAlias"": ""text"",
""text"": ""zoot""
}]
}]";
var expected = json
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
@@ -79,9 +83,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
.Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString());
var component = new NestedContentPropertyHandler();
var result = component.CreateNestedContentKeys(json, false, GuidFactory);
var actual = component.CreateNestedContentKeys(json, false, GuidFactory);
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
AreEqualJson(expected, actual);
}
[Test]
@@ -93,7 +97,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
// and this is how to do that, the result will also include quotes around it.
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
var subJsonEscaped = JsonConvert.ToString(JToken.Parse(@"
[{
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
@@ -104,21 +109,21 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
""ncContentTypeAlias"": ""text"",
""text"": ""zoot""
}
]").ToString());
]").ToString(Formatting.None));
var json = @"[{
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
""text"": ""woot""
}, {
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
""name"": ""Item 2"",
""ncContentTypeAlias"": ""list"",
""text"": ""zoot"",
""subItems"":" + subJsonEscaped + @"
}
]";
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
""text"": ""woot""
}, {
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
""name"": ""Item 2"",
""ncContentTypeAlias"": ""list"",
""text"": ""zoot"",
""subItems"":" + subJsonEscaped + @"
}
]";
var expected = json
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
@@ -127,9 +132,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
.Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString());
var component = new NestedContentPropertyHandler();
var result = component.CreateNestedContentKeys(json, false, GuidFactory);
var actual = component.CreateNestedContentKeys(json, false, GuidFactory);
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
AreEqualJson(expected, actual);
}
[Test]
@@ -141,7 +146,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
// and this is how to do that, the result will also include quotes around it.
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
var subJsonEscaped = JsonConvert.ToString(JToken.Parse(@"[{
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
@@ -152,7 +157,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
""ncContentTypeAlias"": ""text"",
""text"": ""zoot""
}
]").ToString());
]").ToString(Formatting.None));
// Complex editor such as the grid
var complexEditorJsonEscaped = @"{
@@ -231,9 +236,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
.Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString());
var component = new NestedContentPropertyHandler();
var result = component.CreateNestedContentKeys(json, false, GuidFactory);
var actual = component.CreateNestedContentKeys(json, false, GuidFactory);
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
AreEqualJson(expected, actual);
}
[Test]
@@ -252,10 +257,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
var result = component.CreateNestedContentKeys(json, true, GuidFactory);
// Ensure the new GUID is put in a key into the JSON
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString()));
Assert.IsTrue(result.Contains(guids[0].ToString()));
// Ensure that the original key is NOT changed/modified & still exists
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains("04a6dba8-813c-4144-8aca-86a3f24ebf08"));
Assert.IsTrue(result.Contains("04a6dba8-813c-4144-8aca-86a3f24ebf08"));
}
[Test]
@@ -267,7 +272,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
// and this is how to do that, the result will also include quotes around it.
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
var subJsonEscaped = JsonConvert.ToString(JToken.Parse(@"[{
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
""text"": ""woot""
@@ -276,7 +281,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
""ncContentTypeAlias"": ""text"",
""text"": ""zoot""
}
]").ToString());
]").ToString(Formatting.None));
var json = @"[{
""name"": ""Item 1 was copied and has no key"",
@@ -295,9 +300,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
var result = component.CreateNestedContentKeys(json, true, GuidFactory);
// Ensure the new GUID is put in a key into the JSON for each item
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString()));
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString()));
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[2].ToString()));
Assert.IsTrue(result.Contains(guids[0].ToString()));
Assert.IsTrue(result.Contains(guids[1].ToString()));
Assert.IsTrue(result.Contains(guids[2].ToString()));
}
[Test]
@@ -309,7 +314,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
// and this is how to do that, the result will also include quotes around it.
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
var subJsonEscaped = JsonConvert.ToString(JToken.Parse(@"[{
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
""name"": ""Item 1"",
""ncContentTypeAlias"": ""text"",
@@ -319,7 +324,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
""ncContentTypeAlias"": ""text"",
""text"": ""zoot""
}
]").ToString());
]").ToString(Formatting.None));
// Complex editor such as the grid
var complexEditorJsonEscaped = @"{
@@ -394,8 +399,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
var result = component.CreateNestedContentKeys(json, true, GuidFactory);
// Ensure the new GUID is put in a key into the JSON for each item
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString()));
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString()));
Assert.IsTrue(result.Contains(guids[0].ToString()));
Assert.IsTrue(result.Contains(guids[1].ToString()));
}
}
}

View File

@@ -22,13 +22,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
{
private const string CropperJson1 = "{\"focalPoint\": {\"left\": 0.96,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}";
private const string CropperJson2 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}";
private const string CropperJson3 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": []}";
private const string CropperJson3 = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": []}";
private const string MediaPath = "/media/1005/img_0671.jpg";
[Test]
public void CanConvertImageCropperDataSetSrcToString()
{
// cropperJson3 - has not crops
// cropperJson3 - has no crops
ImageCropperValue cropperValue = CropperJson3.DeserializeImageCropperValue();
Attempt<string> serialized = cropperValue.TryConvertTo<string>();
Assert.IsTrue(serialized.Success);
@@ -38,7 +38,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
[Test]
public void CanConvertImageCropperDataSetJObject()
{
// cropperJson3 - has not crops
// cropperJson3 - has no crops
ImageCropperValue cropperValue = CropperJson3.DeserializeImageCropperValue();
Attempt<JObject> serialized = cropperValue.TryConvertTo<JObject>();
Assert.IsTrue(serialized.Success);