diff --git a/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs index c2d65b824f..be2f0b075e 100644 --- a/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs @@ -35,6 +35,12 @@ namespace Umbraco.Core.Persistence /// May return null if the database factory is not configured. string ConnectionString { get; } + /// + /// Gets the provider name. + /// + /// May return null if the database factory is not configured. + string ProviderName { get; } + /// /// Gets a value indicating whether the database factory is configured (see ), /// and it is possible to connect to the database. The factory may however not be initialized (see diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index a4b19ea2f3..bafc3348e9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -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. /// /// - public UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings) + /// + 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 passwordConfig, IGlobalSettings globalSettings) + internal UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IDictionary 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() diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index b113769a1a..61a43d636f 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -124,6 +124,9 @@ namespace Umbraco.Core.Persistence /// public string ConnectionString => _connectionString; + /// + public string ProviderName => _providerName; + /// public bool CanConnect => // actually tries to connect to the database (regardless of configured/initialized) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 9b9da2f926..6b74cab3ed 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -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("Starting unattended install."); - + try { database.BeginTransaction(); diff --git a/src/Umbraco.Core/Security/MachineKeyGenerator.cs b/src/Umbraco.Core/Security/MachineKeyGenerator.cs index a20f04c919..848c7a0983 100644 --- a/src/Umbraco.Core/Security/MachineKeyGenerator.cs +++ b/src/Umbraco.Core/Security/MachineKeyGenerator.cs @@ -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(); + } } } } diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 0bc8de492a..1747b5a939 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -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); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index b2efbd34b8..73430826d6 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -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(),TestObjects.GetGlobalSettings()); + var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of(), TestObjects.GetGlobalSettings(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); repository2.Delete(user); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index b412a8309f..d4c8b3d300 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -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]; }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index d44b10bb80..b433ab5d13 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -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(); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/check-circle.less b/src/Umbraco.Web.UI.Client/src/less/components/check-circle.less index fadf9c7940..a167644ea8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/check-circle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/check-circle.less @@ -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; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index 148b46ead7..1122558b05 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -323,6 +323,7 @@ .umb-group-builder__ungrouped-properties { margin-top: 20px; + position: relative; } /* ---------- GROUPS ---------- */ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html index 15b15a6e4e..c61110adb2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html @@ -94,13 +94,6 @@ -
- -
-
+ + + {{ tabDirectPropertiesDropZoneLabel }} + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js index a5ccf7a9aa..d398c943c4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js @@ -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(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.prevalues.controller.js new file mode 100644 index 0000000000..29a3b876ea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.prevalues.controller.js @@ -0,0 +1,6 @@ +angular.module("umbraco").controller("Umbraco.PrevalueEditors.MarkdownEditorController", + function ($scope) { + if (!$scope.model.value) { + $scope.model.value = "small"; + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.prevalues.html new file mode 100644 index 0000000000..8564a9f490 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.prevalues.html @@ -0,0 +1,9 @@ +
+
+ +
+
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index 1b8d60601f..fc8fa8b809 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -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 diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.prevalues.controller.js new file mode 100644 index 0000000000..3a1e6368e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.prevalues.controller.js @@ -0,0 +1,6 @@ +angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiUrlPickerController", + function ($scope) { + if (!$scope.model.value) { + $scope.model.value = "small"; + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.prevalues.html new file mode 100644 index 0000000000..7abb3c3c01 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.prevalues.html @@ -0,0 +1,9 @@ +
+
+ +
+
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js index 47d1f401c7..24eaba9886 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js @@ -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; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index 96de0cd040..68fb2dbf5f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -46,4 +46,14 @@
+ +
+ +
+
+ diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index c2b16cae96..ec2fd1245c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1743,6 +1743,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont This will also delete all items below this group. Add tab Convert to tab + Drag properties here to place directly on the tab Add language diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 30294b7581..3ecc6b64a4 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -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 /// /// The API controller used for editing content /// - [PluginController("UmbracoApi")] + [Mvc.PluginController("UmbracoApi")] [ValidationFilter] [AngularJsonOnlyConfiguration] [IsBackOffice] + [DisableBrowserCache] public class AuthenticationController : UmbracoApiController { private BackOfficeUserManager _userManager; diff --git a/src/Umbraco.Web/PropertyEditors/MarkdownConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MarkdownConfiguration.cs index 4c2374ccd5..0a65246c7c 100644 --- a/src/Umbraco.Web/PropertyEditors/MarkdownConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MarkdownConfiguration.cs @@ -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; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs index 239569478f..0c18f05dc1 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs @@ -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.")] diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs index f043c8e66e..5c7fc6f14d 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs @@ -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 { /// - /// Converts json block objects into + /// Converts JSON block objects into . /// 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); } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs index 5d216f2b4c..c1915809fa 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs @@ -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($"ConvertPropertyToBlockList ({propertyType.DataType.Id})")) { - var configuration = propertyType.DataType.ConfigurationAs(); - 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(); - var settingsPublishedElements = new Dictionary(); - - var layout = new List(); - 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>(); - // convert the content data + // Get configuration + var configuration = propertyType.DataType.ConfigurationAs(); + 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(); 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(); 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(); 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); diff --git a/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs index 8da8ad23f8..dfa80a337e 100644 --- a/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs +++ b/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs @@ -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") });