Merge branch 'v8/contrib' into v8/dev

This commit is contained in:
Sebastiaan Janssen
2021-09-03 14:08:26 +02:00
28 changed files with 248 additions and 123 deletions

View File

@@ -35,6 +35,12 @@ namespace Umbraco.Core.Persistence
/// <remarks>May return <c>null</c> if the database factory is not configured.</remarks>
string ConnectionString { get; }
/// <summary>
/// Gets the provider name.
/// </summary>
/// <remarks>May return <c>null</c> if the database factory is not configured.</remarks>
string ProviderName { get; }
/// <summary>
/// Gets a value indicating whether the database factory is configured (see <see cref="Configured"/>),
/// and it is possible to connect to the database. The factory may however not be initialized (see

View File

@@ -28,6 +28,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
private readonly IMapperCollection _mapperCollection;
private readonly IGlobalSettings _globalSettings;
private readonly IRuntimeState _runtimeState;
private string _passwordConfigJson;
private bool _passwordConfigInitialized;
@@ -41,19 +42,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// A dictionary specifying the configuration for user passwords. If this is null then no password configuration will be persisted or read.
/// </param>
/// <param name="globalSettings"></param>
public UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings)
/// <param name="runtimeState"></param>
public UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings, IRuntimeState runtimeState)
: base(scopeAccessor, appCaches, logger)
{
_mapperCollection = mapperCollection;
_globalSettings = globalSettings;
_runtimeState = runtimeState;
}
// for tests
internal UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IDictionary<string, string> passwordConfig, IGlobalSettings globalSettings)
internal UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IDictionary<string, string> passwordConfig, IGlobalSettings globalSettings, IRuntimeState runtimeState)
: base(scopeAccessor, appCaches, logger)
{
_mapperCollection = mapperCollection;
_globalSettings = globalSettings;
_runtimeState = runtimeState;
_passwordConfigJson = JsonConvert.SerializeObject(passwordConfig);
_passwordConfigInitialized = true;
}
@@ -86,9 +90,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// This will never resolve to a user, yet this is asked
// for all of the time (especially in cases of members).
// Don't issue a SQL call for this, we know it will not exist.
if (id == default || id < -1)
if (_runtimeState.Level == RuntimeLevel.Upgrade)
{
return null;
// when upgrading people might come from version 7 where user 0 was the default,
// only in upgrade mode do we want to fetch the user of Id 0
if (id < -1)
{
return null;
}
}
else
{
if (id == default || id < -1)
{
return null;
}
}
var sql = SqlContext.Sql()

View File

@@ -124,6 +124,9 @@ namespace Umbraco.Core.Persistence
/// <inheritdoc />
public string ConnectionString => _connectionString;
/// <inheritdoc />
public string ProviderName => _providerName;
/// <inheritdoc />
public bool CanConnect =>
// actually tries to connect to the database (regardless of configured/initialized)

View File

@@ -1,9 +1,9 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Web;
@@ -20,9 +20,7 @@ using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Migrations.Upgrade;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Scoping;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
namespace Umbraco.Core.Runtime
@@ -172,7 +170,7 @@ namespace Umbraco.Core.Runtime
// run handlers
RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory);
// register runtime-level services
// there should be none, really - this is here "just in case"
@@ -276,7 +274,7 @@ namespace Umbraco.Core.Runtime
|| unattendedEmail.IsNullOrWhiteSpace()
|| unattendedPassword.IsNullOrWhiteSpace())
{
fileExists = File.Exists(filePath);
if (fileExists == false)
{
@@ -342,7 +340,7 @@ namespace Umbraco.Core.Runtime
Current.Services.UserService.Save(admin);
// Delete JSON file if it existed to tidy
// Delete JSON file if it existed to tidy
if (fileExists)
{
File.Delete(filePath);
@@ -363,6 +361,19 @@ namespace Umbraco.Core.Runtime
// no connection string set
if (databaseFactory.Configured == false) return;
// create SQL CE database if not existing and database provider is SQL CE
if (databaseFactory.ProviderName == Constants.DbProviderNames.SqlCe)
{
var dataSource = new SqlCeConnectionStringBuilder(databaseFactory.ConnectionString).DataSource;
var dbFilePath = dataSource.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString());
if(File.Exists(dbFilePath) == false)
{
var engine = new SqlCeEngine(databaseFactory.ConnectionString);
engine.CreateDatabase();
}
}
var tries = 5;
var connect = false;
for (var i = 0;;)
@@ -385,7 +396,7 @@ namespace Umbraco.Core.Runtime
// all conditions fulfilled, do the install
Logger.Info<CoreRuntime>("Starting unattended install.");
try
{
database.BeginTransaction();

View File

@@ -71,14 +71,16 @@ namespace Umbraco.Core.Security
private string GenerateKey(int len = 64)
{
var buff = new byte[len / 2];
var rng = new RNGCryptoServiceProvider();
rng.GetBytes(buff);
var sb = new StringBuilder(len);
using (var rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(buff);
var sb = new StringBuilder(len);
for (int i = 0; i < buff.Length; i++)
sb.Append(string.Format("{0:X2}", buff[i]));
for (int i = 0; i < buff.Length; i++)
sb.Append(string.Format("{0:X2}", buff[i]));
return sb.ToString();
return sb.ToString();
}
}
}
}

View File

@@ -703,44 +703,46 @@ namespace Umbraco.Core.Security
if (PasswordFormat == MembershipPasswordFormat.Hashed)
{
var hashAlgorithm = GetHashAlgorithm(pass);
var algorithm = hashAlgorithm as KeyedHashAlgorithm;
if (algorithm != null)
using (var hashAlgorithm = GetHashAlgorithm(pass))
{
var keyedHashAlgorithm = algorithm;
if (keyedHashAlgorithm.Key.Length == saltBytes.Length)
var algorithm = hashAlgorithm as KeyedHashAlgorithm;
if (algorithm != null)
{
//if the salt bytes is the required key length for the algorithm, use it as-is
keyedHashAlgorithm.Key = saltBytes;
}
else if (keyedHashAlgorithm.Key.Length < saltBytes.Length)
{
//if the salt bytes is too long for the required key length for the algorithm, reduce it
var numArray2 = new byte[keyedHashAlgorithm.Key.Length];
Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length);
keyedHashAlgorithm.Key = numArray2;
var keyedHashAlgorithm = algorithm;
if (keyedHashAlgorithm.Key.Length == saltBytes.Length)
{
//if the salt bytes is the required key length for the algorithm, use it as-is
keyedHashAlgorithm.Key = saltBytes;
}
else if (keyedHashAlgorithm.Key.Length < saltBytes.Length)
{
//if the salt bytes is too long for the required key length for the algorithm, reduce it
var numArray2 = new byte[keyedHashAlgorithm.Key.Length];
Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length);
keyedHashAlgorithm.Key = numArray2;
}
else
{
//if the salt bytes is too short for the required key length for the algorithm, extend it
var numArray2 = new byte[keyedHashAlgorithm.Key.Length];
var dstOffset = 0;
while (dstOffset < numArray2.Length)
{
var count = Math.Min(saltBytes.Length, numArray2.Length - dstOffset);
Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count);
dstOffset += count;
}
keyedHashAlgorithm.Key = numArray2;
}
inArray = keyedHashAlgorithm.ComputeHash(bytes);
}
else
{
//if the salt bytes is too short for the required key length for the algorithm, extend it
var numArray2 = new byte[keyedHashAlgorithm.Key.Length];
var dstOffset = 0;
while (dstOffset < numArray2.Length)
{
var count = Math.Min(saltBytes.Length, numArray2.Length - dstOffset);
Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count);
dstOffset += count;
}
keyedHashAlgorithm.Key = numArray2;
var buffer = new byte[saltBytes.Length + bytes.Length];
Buffer.BlockCopy(saltBytes, 0, buffer, 0, saltBytes.Length);
Buffer.BlockCopy(bytes, 0, buffer, saltBytes.Length, bytes.Length);
inArray = hashAlgorithm.ComputeHash(buffer);
}
inArray = keyedHashAlgorithm.ComputeHash(bytes);
}
else
{
var buffer = new byte[saltBytes.Length + bytes.Length];
Buffer.BlockCopy(saltBytes, 0, buffer, 0, saltBytes.Length);
Buffer.BlockCopy(bytes, 0, buffer, saltBytes.Length, bytes.Length);
inArray = hashAlgorithm.ComputeHash(buffer);
}
}
else
@@ -850,7 +852,9 @@ namespace Umbraco.Core.Security
protected internal static string GenerateSalt()
{
var numArray = new byte[16];
new RNGCryptoServiceProvider().GetBytes(numArray);
using (var rng = new RNGCryptoServiceProvider()) {
rng.GetBytes(numArray);
}
return Convert.ToBase64String(numArray);
}

View File

@@ -17,6 +17,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using System;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Tests.Components;
namespace Umbraco.Tests.Persistence.Repositories
{
@@ -67,7 +68,7 @@ namespace Umbraco.Tests.Persistence.Repositories
private UserRepository CreateRepository(IScopeProvider provider)
{
var accessor = (IScopeAccessor) provider;
var repository = new UserRepository(accessor, AppCaches.Disabled, Logger, Mappers, TestObjects.GetGlobalSettings());
var repository = new UserRepository(accessor, AppCaches.Disabled, Logger, Mappers, TestObjects.GetGlobalSettings(), ComponentTests.MockRuntimeState(RuntimeLevel.Run));
return repository;
}
@@ -85,8 +86,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var user = MockedUser.CreateUser();
using (var scope = provider.CreateScope(autoComplete: true))
{
var repository = CreateRepository(provider);
repository.Save(user);
var repository = CreateRepository(provider);
repository.Save(user);
}
using (var scope = provider.CreateScope(autoComplete: true))
@@ -253,7 +254,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var id = user.Id;
var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of<IMapperCollection>(),TestObjects.GetGlobalSettings());
var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of<IMapperCollection>(), TestObjects.GetGlobalSettings(), ComponentTests.MockRuntimeState(RuntimeLevel.Run));
repository2.Delete(user);

View File

@@ -78,11 +78,13 @@
localizationService.localizeMany([
"validation_validation",
"contentTypeEditor_tabHasNoSortOrder",
"general_generic"
"general_generic",
"contentTypeEditor_tabDirectPropertiesDropZone"
]).then(data => {
validationTranslated = data[0];
tabNoSortOrderTranslated = data[1];
scope.genericTab.name = data[2];
scope.tabDirectPropertiesDropZoneLabel = data[3];
});
}

View File

@@ -1543,6 +1543,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
dataTypeKey: args.model.dataTypeKey,
ignoreUserStartNodes: args.model.config.ignoreUserStartNodes,
anchors: anchorValues,
size: args.model.config.editor.overlayWidthSize,
submit: function (model) {
self.insertLinkInEditor(args.editor, model.target, anchorElement);
editorService.close();

View File

@@ -1,19 +1,17 @@
.check_circle {
display: flex;
justify-content: center;
align-items: center;
width: 20px;
height: 20px;
margin: 0 auto;
.icon {
background-color: rgba(0,0,0,.15);
border-radius: 50%;
padding: 3px;
color: @white;
font-size: 1em;
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
float: left;
}
}

View File

@@ -323,6 +323,7 @@
.umb-group-builder__ungrouped-properties {
margin-top: 20px;
position: relative;
}
/* ---------- GROUPS ---------- */

View File

@@ -94,13 +94,6 @@
</div>
<div ng-if="splitViewOpen">
<button type="button" class="btn-reset umb-editor-header__close-split-view" ng-click="closeSplitView()">
<umb-icon icon="icon-delete" class="icon-delete"></umb-icon>
<span class="sr-only"><localize key="general_closepane">Close Pane</localize></span>
</button>
</div>
<div ng-if="editor.variantApps && splitViewOpen !== true">
<umb-editor-navigation
data-element="editor-sub-views"

View File

@@ -120,6 +120,12 @@
ng-click="addNewProperty(tab)">
<localize key="contentTypeEditor_addProperty"></localize>
</button>
<umb-empty-state
ng-if="sortingMode && tab.properties.length === 0"
position="center">
{{ tabDirectPropertiesDropZoneLabel }}
</umb-empty-state>
</umb-box-content>
</umb-box>

View File

@@ -30,6 +30,7 @@ function MarkdownEditorController($scope, $element, assetsService, editorService
function openLinkPicker(callback) {
var linkPicker = {
hideTarget: true,
size: $scope.model.config.overlayWidthSize,
submit: function(model) {
callback(model.target.url, model.target.name);
editorService.close();

View File

@@ -0,0 +1,6 @@
angular.module("umbraco").controller("Umbraco.PrevalueEditors.MarkdownEditorController",
function ($scope) {
if (!$scope.model.value) {
$scope.model.value = "small";
}
});

View File

@@ -0,0 +1,9 @@
<div ng-controller="Umbraco.PrevalueEditors.MarkdownEditorController">
<div class="vertical-align-items">
<select ng-model="model.value">
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
</div>
</div>

View File

@@ -83,6 +83,7 @@ function multiUrlPickerController($scope, localizationService, entityResource, i
dataTypeKey: $scope.model.dataTypeKey,
ignoreUserStartNodes : ($scope.model.config && $scope.model.config.ignoreUserStartNodes) ? $scope.model.config.ignoreUserStartNodes : "0",
hideAnchor: $scope.model.config && $scope.model.config.hideAnchor ? true : false,
size: $scope.model.config.overlayWidthSize,
submit: function (model) {
if (model.target.url || model.target.anchor) {
// if an anchor exists, check that it is appropriately prefixed

View File

@@ -0,0 +1,6 @@
angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiUrlPickerController",
function ($scope) {
if (!$scope.model.value) {
$scope.model.value = "small";
}
});

View File

@@ -0,0 +1,9 @@
<div ng-controller="Umbraco.PrevalueEditors.MultiUrlPickerController">
<div class="vertical-align-items">
<select ng-model="model.value">
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
</div>
</div>

View File

@@ -23,6 +23,10 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
$scope.model.value.mode = "classic";
}
if(!$scope.model.value.overlayWidthSize) {
$scope.model.value.overlayWidthSize = "small";
}
tinyMceService.configuration().then(function(config){
$scope.tinyMceConfig = config;

View File

@@ -46,4 +46,14 @@
</div>
</umb-control-group>
<umb-control-group label="Overlay width size" description="Select overlay width size">
<div class="vertical-align-items">
<select ng-model="model.value.overlayWidthSize">
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
</div>
</umb-control-group>
</div>

View File

@@ -1743,6 +1743,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="confirmDeleteGroupNotice">This will also delete all items below this group.</key>
<key alias="addTab">Add tab</key>
<key alias="convertToTab">Convert to tab</key>
<key alias="tabDirectPropertiesDropZone">Drag properties here to place directly on the tab</key>
</area>
<area alias="languages">
<key alias="addLanguage">Add language</key>

View File

@@ -17,7 +17,6 @@ using Umbraco.Core.Models.Identity;
using Umbraco.Core.Services;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.Security;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
@@ -34,10 +33,11 @@ namespace Umbraco.Web.Editors
/// <summary>
/// The API controller used for editing content
/// </summary>
[PluginController("UmbracoApi")]
[Mvc.PluginController("UmbracoApi")]
[ValidationFilter]
[AngularJsonOnlyConfiguration]
[IsBackOffice]
[DisableBrowserCache]
public class AuthenticationController : UmbracoApiController
{
private BackOfficeUserManager<BackOfficeIdentityUser> _userManager;

View File

@@ -12,5 +12,9 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("defaultValue", "Default value", "textarea", Description = "If value is blank, the editor will show this")]
public string DefaultValue { get; set; }
[ConfigurationField("overlayWidthSize", "Overlay Width Size", "views/propertyeditors/multiurlpicker/multiurlpicker.prevalues.html")]
public string OverlayWidthSize { get; set; }
}
}
}

View File

@@ -11,6 +11,9 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("maxNumber", "Maximum number of items", "number")]
public int MaxNumber { get; set; }
[ConfigurationField("overlayWidthSize", "Overlay Width Size", "views/propertyeditors/multiurlpicker/multiurlpicker.prevalues.html")]
public string OverlayWidthSize { get; set; }
[ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes,
"Ignore User Start Nodes", "boolean",
Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]

View File

@@ -1,6 +1,4 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System;
using Umbraco.Core;
using Umbraco.Core.Models.Blocks;
using Umbraco.Core.Models.PublishedContent;
@@ -10,7 +8,7 @@ using Umbraco.Web.PublishedCache;
namespace Umbraco.Web.PropertyEditors.ValueConverters
{
/// <summary>
/// Converts json block objects into <see cref="IPublishedElement"/>
/// Converts JSON block objects into <see cref="IPublishedElement" />.
/// </summary>
public sealed class BlockEditorConverter
{
@@ -23,30 +21,52 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
_publishedModelFactory = publishedModelFactory;
}
public IPublishedElement ConvertToElement(
BlockItemData data,
PropertyCacheLevel referenceCacheLevel, bool preview)
public IPublishedElement ConvertToElement(BlockItemData data, PropertyCacheLevel referenceCacheLevel, bool preview)
{
// hack! we need to cast, we have no choice beacuse we cannot make breaking changes.
var publishedContentType = GetContentType(data.ContentTypeKey);
// Only convert element types
if (publishedContentType == null || publishedContentType.IsElement == false)
{
return null;
}
var propertyValues = data.RawPropertyValues;
// Get the UDI from the deserialized object. If this is empty, we can fallback to checking the 'key' if there is one
var key = (data.Udi is GuidUdi gudi) ? gudi.Guid : Guid.Empty;
if (key == Guid.Empty && propertyValues.TryGetValue("key", out var keyo))
{
Guid.TryParse(keyo.ToString(), out key);
}
IPublishedElement element = new PublishedElement(publishedContentType, key, propertyValues, preview, referenceCacheLevel, _publishedSnapshotAccessor);
element = _publishedModelFactory.CreateModel(element);
return element;
}
public Type GetModelType(Guid contentTypeKey)
{
var publishedContentType = GetContentType(contentTypeKey);
if (publishedContentType != null)
{
var modelType = ModelType.For(publishedContentType.Alias);
return _publishedModelFactory.MapModelType(modelType);
}
return typeof(IPublishedElement);
}
private IPublishedContentType GetContentType(Guid contentTypeKey)
{
// HACK! We need to cast, we have no choice because we can't make breaking changes (and we need the GUID overload)
var publishedContentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content as IPublishedContentCache2;
if (publishedContentCache == null)
throw new InvalidOperationException("The published content cache is not " + typeof(IPublishedContentCache2));
// only convert element types - content types will cause an exception when PublishedModelFactory creates the model
var publishedContentType = publishedContentCache.GetContentType(data.ContentTypeKey);
if (publishedContentType == null || publishedContentType.IsElement == false)
return null;
var propertyValues = data.RawPropertyValues;
// Get the udi from the deserialized object. If this is empty we can fallback to checking the 'key' if there is one
var key = (data.Udi is GuidUdi gudi) ? gudi.Guid : Guid.Empty;
if (propertyValues.TryGetValue("key", out var keyo))
Guid.TryParse(keyo.ToString(), out key);
IPublishedElement element = new PublishedElement(publishedContentType, key, propertyValues, preview, referenceCacheLevel, _publishedSnapshotAccessor);
element = _publishedModelFactory.CreateModel(element);
return element;
return publishedContentCache.GetContentType(contentTypeKey);
}
}
}

View File

@@ -1,6 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
@@ -51,16 +49,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
using (_proflog.DebugDuration<BlockListPropertyValueConverter>($"ConvertPropertyToBlockList ({propertyType.DataType.Id})"))
{
var configuration = propertyType.DataType.ConfigurationAs<BlockListConfiguration>();
var blockConfigMap = configuration.Blocks.ToDictionary(x => x.ContentElementTypeKey);
var validSettingElementTypes = blockConfigMap.Values.Select(x => x.SettingsElementTypeKey).Where(x => x.HasValue).Distinct().ToList();
var contentPublishedElements = new Dictionary<Guid, IPublishedElement>();
var settingsPublishedElements = new Dictionary<Guid, IPublishedElement>();
var layout = new List<BlockListItem>();
var value = (string)inter;
// Short-circuit on empty values
if (string.IsNullOrWhiteSpace(value)) return BlockListModel.Empty;
var converted = _blockListEditorDataConverter.Deserialize(value);
@@ -68,49 +59,60 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
var blockListLayout = converted.Layout.ToObject<IEnumerable<BlockListLayoutItem>>();
// convert the content data
// Get configuration
var configuration = propertyType.DataType.ConfigurationAs<BlockListConfiguration>();
var blockConfigMap = configuration.Blocks.ToDictionary(x => x.ContentElementTypeKey);
var validSettingsElementTypes = blockConfigMap.Values.Select(x => x.SettingsElementTypeKey).Where(x => x.HasValue).Distinct().ToList();
// Convert the content data
var contentPublishedElements = new Dictionary<Guid, IPublishedElement>();
foreach (var data in converted.BlockValue.ContentData)
{
if (!blockConfigMap.ContainsKey(data.ContentTypeKey)) continue;
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
if (element == null) continue;
contentPublishedElements[element.Key] = element;
}
// convert the settings data
// If there are no content elements, it doesn't matter what is stored in layout
if (contentPublishedElements.Count == 0) return BlockListModel.Empty;
// Convert the settings data
var settingsPublishedElements = new Dictionary<Guid, IPublishedElement>();
foreach (var data in converted.BlockValue.SettingsData)
{
if (!validSettingElementTypes.Contains(data.ContentTypeKey)) continue;
if (!validSettingsElementTypes.Contains(data.ContentTypeKey)) continue;
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
if (element == null) continue;
settingsPublishedElements[element.Key] = element;
}
// if there's no elements just return since if there's no data it doesn't matter what is stored in layout
if (contentPublishedElements.Count == 0) return BlockListModel.Empty;
var layout = new List<BlockListItem>();
foreach (var layoutItem in blockListLayout)
{
// get the content reference
var contentGuidUdi = (GuidUdi)layoutItem.ContentUdi;
// Get the content reference
var contentGuidUdi = (GuidUdi)layoutItem.ContentUdi;
if (!contentPublishedElements.TryGetValue(contentGuidUdi.Guid, out var contentData))
continue;
// get the setting reference
IPublishedElement settingsData = null;
var settingGuidUdi = layoutItem.SettingsUdi != null ? (GuidUdi)layoutItem.SettingsUdi : null;
if (settingGuidUdi != null)
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
if (!contentData.ContentType.TryGetKey(out var contentTypeKey))
throw new InvalidOperationException("The content type was not of type " + typeof(IPublishedContentType2));
if (!blockConfigMap.TryGetValue(contentTypeKey, out var blockConfig))
continue;
// this can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again
// we also ensure that the content type's match since maybe the settings type has been changed after this has been persisted.
// Get the setting reference
IPublishedElement settingsData = null;
var settingGuidUdi = layoutItem.SettingsUdi != null ? (GuidUdi)layoutItem.SettingsUdi : null;
if (settingGuidUdi != null)
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
// This can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again
// We also ensure that the content types match, since maybe the settings type has been changed after this has been persisted
if (settingsData != null)
{
if (!settingsData.ContentType.TryGetKey(out var settingsElementTypeKey))
@@ -120,8 +122,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
settingsData = null;
}
// Get settings type from configuration
var settingsType = blockConfig.SettingsElementTypeKey.HasValue
? _blockConverter.GetModelType(blockConfig.SettingsElementTypeKey.Value)
: typeof(IPublishedElement);
// TODO: This should be optimized/cached, as calling Activator.CreateInstance is slow
var layoutType = typeof(BlockListItem<,>).MakeGenericType(contentData.GetType(), settingsData?.GetType() ?? typeof(IPublishedElement));
var layoutType = typeof(BlockListItem<,>).MakeGenericType(contentData.GetType(), settingsType);
var layoutRef = (BlockListItem)Activator.CreateInstance(layoutType, contentGuidUdi, contentData, settingGuidUdi, settingsData);
layout.Add(layoutRef);

View File

@@ -62,7 +62,7 @@ namespace Umbraco.Web.Security
response.ContentType = "application/json; charset=utf-8";
response.StatusCode = 200;
response.Headers.Add("Cache-Control", new[] { "no-cache" });
response.Headers.Add("Cache-Control", new[] { "no-store", "must-revalidate", "no-cache", "max-age=0" });
response.Headers.Add("Pragma", new[] { "no-cache" });
response.Headers.Add("Expires", new[] { "-1" });
response.Headers.Add("Date", new[] { _authOptions.SystemClock.UtcNow.ToString("R") });