Merge branch 'v8/contrib' into v8/dev
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +323,7 @@
|
||||
|
||||
.umb-group-builder__ungrouped-properties {
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ---------- GROUPS ---------- */
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
angular.module("umbraco").controller("Umbraco.PrevalueEditors.MarkdownEditorController",
|
||||
function ($scope) {
|
||||
if (!$scope.model.value) {
|
||||
$scope.model.value = "small";
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiUrlPickerController",
|
||||
function ($scope) {
|
||||
if (!$scope.model.value) {
|
||||
$scope.model.value = "small";
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.")]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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") });
|
||||
|
||||
Reference in New Issue
Block a user