From 9ed1101f273baecfe9e75138e6802533a75cbee0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Aug 2019 11:16:24 +1000 Subject: [PATCH 01/19] whitespace changes --- .../users/changepassword.directive.js | 298 +++++++++--------- .../components/users/change-password.html | 10 +- 2 files changed, 154 insertions(+), 154 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js index f8dbefa5d7..8919a26dcb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js @@ -1,167 +1,167 @@ (function () { - 'use strict'; + 'use strict'; - function ChangePasswordController($scope) { + function ChangePasswordController($scope) { - function resetModel(isNew) { - //the model config will contain an object, if it does not we'll create defaults - //NOTE: We will not support doing the password regex on the client side because the regex on the server side - //based on the membership provider cannot always be ported to js from .net directly. - /* - { - hasPassword: true/false, - requiresQuestionAnswer: true/false, - enableReset: true/false, - enablePasswordRetrieval: true/false, - minPasswordLength: 10 - } - */ + function resetModel(isNew) { + //the model config will contain an object, if it does not we'll create defaults + //NOTE: We will not support doing the password regex on the client side because the regex on the server side + //based on the membership provider cannot always be ported to js from .net directly. + /* + { + hasPassword: true/false, + requiresQuestionAnswer: true/false, + enableReset: true/false, + enablePasswordRetrieval: true/false, + minPasswordLength: 10 + } + */ - $scope.showReset = false; + $scope.showReset = false; - //set defaults if they are not available - if ($scope.config.disableToggle === undefined) { - $scope.config.disableToggle = false; - } - if ($scope.config.hasPassword === undefined) { - $scope.config.hasPassword = false; - } - if ($scope.config.enablePasswordRetrieval === undefined) { - $scope.config.enablePasswordRetrieval = true; - } - if ($scope.config.requiresQuestionAnswer === undefined) { - $scope.config.requiresQuestionAnswer = false; - } - //don't enable reset if it is new - that doesn't make sense - if (isNew === "true") { - $scope.config.enableReset = false; - } - else if ($scope.config.enableReset === undefined) { - $scope.config.enableReset = true; - } - - if ($scope.config.minPasswordLength === undefined) { - $scope.config.minPasswordLength = 0; - } - - //set the model defaults - if (!angular.isObject($scope.passwordValues)) { - //if it's not an object then just create a new one - $scope.passwordValues = { - newPassword: null, - oldPassword: null, - reset: null, - answer: null - }; - } - else { - //just reset the values + //set defaults if they are not available + if ($scope.config.disableToggle === undefined) { + $scope.config.disableToggle = false; + } + if ($scope.config.hasPassword === undefined) { + $scope.config.hasPassword = false; + } + if ($scope.config.enablePasswordRetrieval === undefined) { + $scope.config.enablePasswordRetrieval = true; + } + if ($scope.config.requiresQuestionAnswer === undefined) { + $scope.config.requiresQuestionAnswer = false; + } + //don't enable reset if it is new - that doesn't make sense + if (isNew === "true") { + $scope.config.enableReset = false; + } + else if ($scope.config.enableReset === undefined) { + $scope.config.enableReset = true; + } + + if ($scope.config.minPasswordLength === undefined) { + $scope.config.minPasswordLength = 0; + } + + //set the model defaults + if (!angular.isObject($scope.passwordValues)) { + //if it's not an object then just create a new one + $scope.passwordValues = { + newPassword: null, + oldPassword: null, + reset: null, + answer: null + }; + } + else { + //just reset the values + + if (!isNew) { + //if it is new, then leave the generated pass displayed + $scope.passwordValues.newPassword = null; + $scope.passwordValues.oldPassword = null; + } + $scope.passwordValues.reset = null; + $scope.passwordValues.answer = null; + } + + //the value to compare to match passwords + if (!isNew) { + $scope.passwordValues.confirm = ""; + } + else if ($scope.passwordValues.newPassword && $scope.passwordValues.newPassword.length > 0) { + //if it is new and a new password has been set, then set the confirm password too + $scope.passwordValues.confirm = $scope.passwordValues.newPassword; + } - if (!isNew) { - //if it is new, then leave the generated pass displayed - $scope.passwordValues.newPassword = null; - $scope.passwordValues.oldPassword = null; } - $scope.passwordValues.reset = null; - $scope.passwordValues.answer = null; - } - //the value to compare to match passwords - if (!isNew) { - $scope.passwordValues.confirm = ""; - } - else if ($scope.passwordValues.newPassword && $scope.passwordValues.newPassword.length > 0) { - //if it is new and a new password has been set, then set the confirm password too - $scope.passwordValues.confirm = $scope.passwordValues.newPassword; - } + resetModel($scope.isNew); + + //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there + //with validators turned on. + $scope.changing = $scope.config.disableToggle === true || !$scope.config.hasPassword; + + //we're not currently changing so set the model to null + if (!$scope.changing) { + $scope.passwordValues = null; + } + + $scope.doChange = function () { + resetModel(); + $scope.changing = true; + //if there was a previously generated password displaying, clear it + $scope.passwordValues.generatedPassword = null; + $scope.passwordValues.confirm = null; + }; + + $scope.cancelChange = function () { + $scope.changing = false; + //set model to null + $scope.passwordValues = null; + }; + + var unsubscribe = []; + + //listen for the saved event, when that occurs we'll + //change to changing = false; + unsubscribe.push($scope.$on("formSubmitted", function () { + if ($scope.config.disableToggle === false) { + $scope.changing = false; + } + })); + unsubscribe.push($scope.$on("formSubmitting", function () { + //if there was a previously generated password displaying, clear it + if ($scope.changing && $scope.passwordValues) { + $scope.passwordValues.generatedPassword = null; + } + else if (!$scope.changing) { + //we are not changing, so the model needs to be null + $scope.passwordValues = null; + } + })); + + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + + $scope.showOldPass = function () { + return $scope.config.hasPassword && + !$scope.config.allowManuallyChangingPassword && + !$scope.config.enablePasswordRetrieval && !$scope.showReset; + }; + + // TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this directive + $scope.showCancelBtn = function () { + return $scope.config.disableToggle !== true && $scope.config.hasPassword; + }; } - resetModel($scope.isNew); + function ChangePasswordDirective() { - //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there - //with validators turned on. - $scope.changing = $scope.config.disableToggle === true || !$scope.config.hasPassword; + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/users/change-password.html', + controller: 'Umbraco.Editors.Users.ChangePasswordDirectiveController', + scope: { + isNew: "=?", + passwordValues: "=", + config: "=" + } + }; + + return directive; - //we're not currently changing so set the model to null - if (!$scope.changing) { - $scope.passwordValues = null; } - $scope.doChange = function () { - resetModel(); - $scope.changing = true; - //if there was a previously generated password displaying, clear it - $scope.passwordValues.generatedPassword = null; - $scope.passwordValues.confirm = null; - }; - - $scope.cancelChange = function () { - $scope.changing = false; - //set model to null - $scope.passwordValues = null; - }; - - var unsubscribe = []; - - //listen for the saved event, when that occurs we'll - //change to changing = false; - unsubscribe.push($scope.$on("formSubmitted", function () { - if ($scope.config.disableToggle === false) { - $scope.changing = false; - } - })); - unsubscribe.push($scope.$on("formSubmitting", function () { - //if there was a previously generated password displaying, clear it - if ($scope.changing && $scope.passwordValues) { - $scope.passwordValues.generatedPassword = null; - } - else if (!$scope.changing) { - //we are not changing, so the model needs to be null - $scope.passwordValues = null; - } - })); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - - $scope.showOldPass = function () { - return $scope.config.hasPassword && - !$scope.config.allowManuallyChangingPassword && - !$scope.config.enablePasswordRetrieval && !$scope.showReset; - }; - - // TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this directive - $scope.showCancelBtn = function () { - return $scope.config.disableToggle !== true && $scope.config.hasPassword; - }; - - } - - function ChangePasswordDirective() { - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/users/change-password.html', - controller: 'Umbraco.Editors.Users.ChangePasswordDirectiveController', - scope: { - isNew: "=?", - passwordValues: "=", - config: "=" - } - }; - - return directive; - - } - - angular.module('umbraco.directives').controller('Umbraco.Editors.Users.ChangePasswordDirectiveController', ChangePasswordController); - angular.module('umbraco.directives').directive('changePassword', ChangePasswordDirective); + angular.module('umbraco.directives').controller('Umbraco.Editors.Users.ChangePasswordDirectiveController', ChangePasswordController); + angular.module('umbraco.directives').directive('changePassword', ChangePasswordDirective); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html index 7b6c7ca0e1..150fbf892b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html @@ -19,10 +19,10 @@ val-server-field="resetPassword" no-dirty-check ng-change="showReset = !showReset" /> - + {{passwordForm.resetPassword.errorMsg}} - + @@ -32,7 +32,7 @@ required val-server-field="oldPassword" no-dirty-check /> - + Required {{passwordForm.oldPassword.errorMsg}} @@ -45,7 +45,7 @@ val-server-field="password" ng-minlength="{{config.minPasswordLength}}" no-dirty-check /> - + Required Minimum {{config.minPasswordLength}} characters {{passwordForm.password.errorMsg}} @@ -57,7 +57,7 @@ class="input-block-level umb-textstring textstring" val-compare="password" no-dirty-check /> - + The confirmed password doesn't match the new password! From d2be30a228af13e34967e89fe11010749b1bad1b Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Aug 2019 12:26:49 +1000 Subject: [PATCH 02/19] converting to component --- .../users/changepassword.directive.js | 191 +++++++++--------- .../components/users/change-password.html | 55 ++--- 2 files changed, 128 insertions(+), 118 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js index 8919a26dcb..97eb2bf708 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js @@ -3,6 +3,17 @@ function ChangePasswordController($scope) { + var vm = this; + + vm.$onInit = onInit; + vm.$onDestroy = onDestroy; + vm.doChange = doChange; + vm.cancelChange = cancelChange; + vm.showOldPass = showOldPass; + vm.showCancelBtn = showCancelBtn; + + var unsubscribe = []; + function resetModel(isNew) { //the model config will contain an object, if it does not we'll create defaults //NOTE: We will not support doing the password regex on the client side because the regex on the server side @@ -17,37 +28,37 @@ } */ - $scope.showReset = false; + vm.showReset = false; //set defaults if they are not available - if ($scope.config.disableToggle === undefined) { - $scope.config.disableToggle = false; + if (vm.config.disableToggle === undefined) { + vm.config.disableToggle = false; } - if ($scope.config.hasPassword === undefined) { - $scope.config.hasPassword = false; + if (vm.config.hasPassword === undefined) { + vm.config.hasPassword = false; } - if ($scope.config.enablePasswordRetrieval === undefined) { - $scope.config.enablePasswordRetrieval = true; + if (vm.config.enablePasswordRetrieval === undefined) { + vm.config.enablePasswordRetrieval = true; } - if ($scope.config.requiresQuestionAnswer === undefined) { - $scope.config.requiresQuestionAnswer = false; + if (vm.config.requiresQuestionAnswer === undefined) { + vm.config.requiresQuestionAnswer = false; } //don't enable reset if it is new - that doesn't make sense if (isNew === "true") { - $scope.config.enableReset = false; + vm.config.enableReset = false; } - else if ($scope.config.enableReset === undefined) { - $scope.config.enableReset = true; + else if (vm.config.enableReset === undefined) { + vm.config.enableReset = true; } - if ($scope.config.minPasswordLength === undefined) { - $scope.config.minPasswordLength = 0; + if (vm.config.minPasswordLength === undefined) { + vm.config.minPasswordLength = 0; } //set the model defaults - if (!angular.isObject($scope.passwordValues)) { + if (!angular.isObject(vm.passwordValues)) { //if it's not an object then just create a new one - $scope.passwordValues = { + vm.passwordValues = { newPassword: null, oldPassword: null, reset: null, @@ -59,109 +70,103 @@ if (!isNew) { //if it is new, then leave the generated pass displayed - $scope.passwordValues.newPassword = null; - $scope.passwordValues.oldPassword = null; + vm.passwordValues.newPassword = null; + vm.passwordValues.oldPassword = null; } - $scope.passwordValues.reset = null; - $scope.passwordValues.answer = null; + vm.passwordValues.reset = null; + vm.passwordValues.answer = null; } //the value to compare to match passwords if (!isNew) { - $scope.passwordValues.confirm = ""; + vm.passwordValues.confirm = ""; } - else if ($scope.passwordValues.newPassword && $scope.passwordValues.newPassword.length > 0) { + else if (vm.passwordValues.newPassword && vm.passwordValues.newPassword.length > 0) { //if it is new and a new password has been set, then set the confirm password too - $scope.passwordValues.confirm = $scope.passwordValues.newPassword; + vm.passwordValues.confirm = vm.passwordValues.newPassword; } } - resetModel($scope.isNew); - - //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there - //with validators turned on. - $scope.changing = $scope.config.disableToggle === true || !$scope.config.hasPassword; - - //we're not currently changing so set the model to null - if (!$scope.changing) { - $scope.passwordValues = null; - } - - $scope.doChange = function () { - resetModel(); - $scope.changing = true; - //if there was a previously generated password displaying, clear it - $scope.passwordValues.generatedPassword = null; - $scope.passwordValues.confirm = null; - }; - - $scope.cancelChange = function () { - $scope.changing = false; - //set model to null - $scope.passwordValues = null; - }; - - var unsubscribe = []; - - //listen for the saved event, when that occurs we'll - //change to changing = false; - unsubscribe.push($scope.$on("formSubmitted", function () { - if ($scope.config.disableToggle === false) { - $scope.changing = false; - } - })); - unsubscribe.push($scope.$on("formSubmitting", function () { - //if there was a previously generated password displaying, clear it - if ($scope.changing && $scope.passwordValues) { - $scope.passwordValues.generatedPassword = null; - } - else if (!$scope.changing) { - //we are not changing, so the model needs to be null - $scope.passwordValues = null; - } - })); - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { + function onDestroy() { for (var u in unsubscribe) { unsubscribe[u](); } - }); + } - $scope.showOldPass = function () { - return $scope.config.hasPassword && - !$scope.config.allowManuallyChangingPassword && - !$scope.config.enablePasswordRetrieval && !$scope.showReset; - }; + function onInit() { + //listen for the saved event, when that occurs we'll + //change to changing = false; + unsubscribe.push($scope.$on("formSubmitted", function () { + if (vm.config.disableToggle === false) { + vm.changing = false; + } + })); - // TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this directive - $scope.showCancelBtn = function () { - return $scope.config.disableToggle !== true && $scope.config.hasPassword; - }; + unsubscribe.push($scope.$on("formSubmitting", function () { + //if there was a previously generated password displaying, clear it + if (vm.changing && vm.passwordValues) { + vm.passwordValues.generatedPassword = null; + } + else if (!vm.changing) { + //we are not changing, so the model needs to be null + vm.passwordValues = null; + } + })); - } + resetModel(vm.isNew); - function ChangePasswordDirective() { + //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there + //with validators turned on. + vm.changing = vm.config.disableToggle === true || !vm.config.hasPassword; - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/users/change-password.html', - controller: 'Umbraco.Editors.Users.ChangePasswordDirectiveController', - scope: { - isNew: "=?", - passwordValues: "=", - config: "=" + //we're not currently changing so set the model to null + if (!vm.changing) { + vm.passwordValues = null; } + } + + function doChange() { + resetModel(); + vm.changing = true; + //if there was a previously generated password displaying, clear it + vm.passwordValues.generatedPassword = null; + vm.passwordValues.confirm = null; }; - return directive; + function cancelChange() { + vm.changing = false; + //set model to null + vm.passwordValues = null; + }; + + function showOldPass() { + return vm.config.hasPassword && + !vm.config.allowManuallyChangingPassword && + !vm.config.enablePasswordRetrieval && !vm.showReset; + }; + + // TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this component + function showCancelBtn() { + return vm.config.disableToggle !== true && vm.config.hasPassword; + }; } - angular.module('umbraco.directives').controller('Umbraco.Editors.Users.ChangePasswordDirectiveController', ChangePasswordController); - angular.module('umbraco.directives').directive('changePassword', ChangePasswordDirective); + var component = { + templateUrl: 'views/components/users/change-password.html', + controller: ChangePasswordController, + controllerAs: 'vm', + bindings: { + isNew: "<", + passwordValues: "=", //TODO: Do we need bi-directional vals? + config: "=" //TODO: Do we need bi-directional vals? + //TODO: Do we need callbacks? + } + }; + + angular.module('umbraco.directives').component('changePassword', component); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html index 150fbf892b..026918231e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html @@ -1,68 +1,73 @@
-
+
Password has been reset to:
- {{passwordValues.generatedPassword}} + {{vm.passwordValues.generatedPassword}}
-
+
- - - Hello +
{{vm.showReset}}
+
{{vm.passwordValues | json}}
+ + + + - - {{passwordForm.resetPassword.errorMsg}} + ng-change="vm.showReset = !vm.showReset" /> + + {{vm.passwordForm.resetPassword.errorMsg}} - +
- - + - + Required - {{passwordForm.oldPassword.errorMsg}} + {{vm.passwordForm.oldPassword.errorMsg}} - - + - + Required - Minimum {{config.minPasswordLength}} characters - {{passwordForm.password.errorMsg}} + Minimum {{vm.config.minPasswordLength}} characters + {{vm.passwordForm.password.errorMsg}} - - + - + The confirmed password doesn't match the new password! - + Cancel From df391c86f379f6a7d514ac3a9238e4e0632f2613 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Aug 2019 12:45:39 +1000 Subject: [PATCH 03/19] Fixes re-syncing member and media data after saving --- .../src/common/services/contenteditinghelper.service.js | 4 ++-- .../src/views/components/users/change-password.html | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 732f682082..a1c1bdcae4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -484,7 +484,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt var savedVariants = []; if (origContent.variants) { isContent = true; - //it's contnet so assign the variants as they exist + //it's content so assign the variants as they exist origVariants = origContent.variants; savedVariants = savedContent.variants; } @@ -510,7 +510,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //special case for content, don't sync this variant if it wasn't tagged //for saving in the first place - if (!origVariant.save) { + if (isContent && !origVariant.save) { continue; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html index 026918231e..ef5f7a4159 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html @@ -12,10 +12,6 @@
-
Hello
-
{{vm.showReset}}
-
{{vm.passwordValues | json}}
- Date: Wed, 14 Aug 2019 14:49:05 +1000 Subject: [PATCH 04/19] Fixes dropping unique constraint indexes and oddly named FKs --- .../DeleteKeysAndIndexesBuilder.cs | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs index 9b13457b76..df74bf7c87 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs @@ -1,5 +1,7 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using NPoco; +using Umbraco.Core; using Umbraco.Core.Migrations.Expressions.Common; using Umbraco.Core.Persistence.SqlSyntax; @@ -27,31 +29,57 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.KeysAndIndexes { _context.BuildingExpression = false; + //get a list of all constraints - this will include all PK, FK and unique constraints + var tableConstraints = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList(); + + //get a list of defined indexes - this will include all indexes, unique indexes and unique constraint indexes + var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList(); + + var uniqueConstraintNames = tableConstraints.Where(x => !x.Item2.InvariantStartsWith("PK_") && !x.Item2.InvariantStartsWith("FK_")).Select(x => x.Item2); + var indexNames = indexes.Select(x => x.IndexName).ToList(); + // drop keys if (DeleteLocal || DeleteForeign) { // table, constraint - var tableKeys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList(); + if (DeleteForeign) { - foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_"))) + //In some cases not all FK's are prefixed with "FK" :/ mostly with old upgraded databases so we need to check if it's either: + // * starts with FK OR + // * doesn't start with PK_ and doesn't exist in the list of indexes + + foreach (var key in tableConstraints.Where(x => x.Item1 == TableName + && (x.Item2.InvariantStartsWith("FK_") || (!x.Item2.InvariantStartsWith("PK_") && !indexNames.InvariantContains(x.Item2))))) + { Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do(); + } + } if (DeleteLocal) { - foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_"))) + foreach (var key in tableConstraints.Where(x => x.Item1 == TableName && x.Item2.InvariantStartsWith("PK_"))) Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do(); - // note: we do *not* delete the DEFAULT constraints + // note: we do *not* delete the DEFAULT constraints and if we wanted to we'd have to deal with that in interesting ways + // since SQL server has a specific way to handle that, see SqlServerSyntaxProvider.GetDefaultConstraintsPerColumn } } // drop indexes if (DeleteLocal) - { - var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList(); + { foreach (var index in indexes.Where(x => x.TableName == TableName)) - Delete.Index(index.IndexName).OnTable(index.TableName).Do(); + { + //if this is a unique constraint we need to drop the constraint, else drop the index + //to figure this out, the index must be tagged as unique and it must exist in the tableConstraints + + if (index.IsUnique && uniqueConstraintNames.InvariantContains(index.IndexName)) + Delete.UniqueConstraint(index.IndexName).FromTable(index.TableName).Do(); + else + Delete.Index(index.IndexName).OnTable(index.TableName).Do(); + } + } } From 7e8364eae48be9bbd3a160c3f3aab03eb374ab1f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 15 Aug 2019 13:10:17 +0200 Subject: [PATCH 05/19] V8: Support mixed element variance in Nested Content (#5952) (cherry picked from commit e7a9ee8142b45de6633a10b923d84e84f0b9d0ef) --- .../nestedcontent/nestedcontent.controller.js | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 7ead8974d8..aaf7ed08bb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -489,32 +489,30 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop node.key = fromNcEntry && fromNcEntry.key ? fromNcEntry.key : String.CreateGuid(); - for (var v = 0; v < node.variants.length; v++) { - var variant = node.variants[v]; - - for (var t = 0; t < variant.tabs.length; t++) { - var tab = variant.tabs[t]; + var variant = node.variants[0]; + + for (var t = 0; t < variant.tabs.length; t++) { + var tab = variant.tabs[t]; - for (var p = 0; p < tab.properties.length; p++) { - var prop = tab.properties[p]; + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; - prop.propertyAlias = prop.alias; - prop.alias = $scope.model.alias + "___" + prop.alias; - // Force validation to occur server side as this is the - // only way we can have consistency between mandatory and - // regex validation messages. Not ideal, but it works. - prop.validation = { - mandatory: false, - pattern: "" - }; + prop.propertyAlias = prop.alias; + prop.alias = $scope.model.alias + "___" + prop.alias; + // Force validation to occur server side as this is the + // only way we can have consistency between mandatory and + // regex validation messages. Not ideal, but it works. + prop.validation = { + mandatory: false, + pattern: "" + }; - if (fromNcEntry && fromNcEntry[prop.propertyAlias]) { - prop.value = fromNcEntry[prop.propertyAlias]; - } + if (fromNcEntry && fromNcEntry[prop.propertyAlias]) { + prop.value = fromNcEntry[prop.propertyAlias]; } } } - + $scope.nodes.push(node); return node; From a0af474c757a48d6680ff005aa2e54f4090dadd2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 15 Aug 2019 13:29:14 +0200 Subject: [PATCH 06/19] Bump version to 8.1.3 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 5feffc0b57..2da07d523f 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.1.2")] -[assembly: AssemblyInformationalVersion("8.1.2")] +[assembly: AssemblyFileVersion("8.1.3")] +[assembly: AssemblyInformationalVersion("8.1.3")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index b5d908f32e..fada1cf9c4 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -345,9 +345,9 @@ False True - 8120 + 8130 / - http://localhost:8120 + http://localhost:8130 False False From 455395154a569acc7a4c0c1f04d2f8849eae8a67 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 14 Aug 2019 21:25:02 +0200 Subject: [PATCH 07/19] Don't reuse previously used content templates (cherry picked from commit 3087eff147c12c73220515df53f0a264c489fc79) --- .../src/views/content/content.create.controller.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js index f906369926..1413965a19 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js @@ -51,7 +51,10 @@ function contentCreateController($scope, .search("create", "true") /* when we create a new node we want to make sure it uses the same language as what is selected in the tree */ - .search("cculture", mainCulture); + .search("cculture", mainCulture) + /* when we create a new node we must make sure that any previously + used blueprint is reset */ + .search("blueprintId", null); close(); } From f8409be50673615f015f63efbe9e1b6962a40328 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 14 Aug 2019 08:50:25 +0200 Subject: [PATCH 08/19] Use an Umbraco confirm dialog when deleting groups (cherry picked from commit f1f9e1742ebf529e18e9f87c22581195c95a4760) --- .../users/views/groups/groups.controller.js | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js index 9c476103a5..1e51d4585e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js @@ -2,7 +2,7 @@ "use strict"; function UserGroupsController($scope, $timeout, $location, $filter, userService, userGroupsResource, - formHelper, localizationService, listViewHelper) { + formHelper, localizationService, listViewHelper, overlayService) { var vm = this; @@ -95,18 +95,26 @@ if(vm.selection.length > 0) { - localizationService.localize("defaultdialogs_confirmdelete") - .then(function(value) { - - var confirmResponse = confirm(value); - - if (confirmResponse === true) { - userGroupsResource.deleteUserGroups(_.pluck(vm.selection, "id")).then(function (data) { - clearSelection(); - onInit(); - }, angular.noop); - } - + localizationService.localizeMany(["general_delete", "defaultdialogs_confirmdelete", "general_cancel", "contentTypeEditor_yesDelete"]) + .then(function (data) { + const overlay = { + title: data[0], + content: data[1] + "?", + closeButtonLabel: data[2], + submitButtonLabel: data[3], + submitButtonStyle: "danger", + close: function () { + overlayService.close(); + }, + submit: function () { + userGroupsResource.deleteUserGroups(_.pluck(vm.selection, "id")).then(function (data) { + clearSelection(); + onInit(); + }, angular.noop); + overlayService.close(); + } + }; + overlayService.open(overlay); }); } From 16ac2c5589dabf65747b95e7122e58b9a82858d3 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 13 Aug 2019 21:14:28 +0200 Subject: [PATCH 09/19] Don't change parent protection when editing child protection (cherry picked from commit 4743fc0e1d4b76cbf19aa1af3f858ad2bb65e61b) --- src/Umbraco.Web/Editors/ContentController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 4e420206d3..9d5af028e3 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2297,7 +2297,7 @@ namespace Umbraco.Web.Editors } var entry = Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) + if (entry == null || entry.ProtectedNodeId != content.Id) { return Request.CreateResponse(HttpStatusCode.OK); } @@ -2379,7 +2379,7 @@ namespace Umbraco.Web.Editors var entry = Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) + if (entry == null || entry.ProtectedNodeId != content.Id) { entry = new PublicAccessEntry(content, loginPage, errorPage, new List()); From 65b2d08997a44b6d10cff62b710e4257bdd92e3b Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 8 Aug 2019 23:35:58 +0200 Subject: [PATCH 10/19] Set media picker dirty when the value changes (cherry picked from commit 2b7154622ef99771cefa7e87b52513ea159c0a05) --- .../mediapicker/mediapicker.controller.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 120407ab10..e59e2bb26a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //this controller simply tells the dialogs service to open a mediaPicker window //with a specified callback, this callback will receive an object with a selection on it angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", - function ($scope, entityResource, mediaHelper, $timeout, userService, localizationService, editorService) { + function ($scope, entityResource, mediaHelper, $timeout, userService, localizationService, editorService, angularHelper) { var vm = { labels: { @@ -92,6 +92,10 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl $scope.model.value = $scope.ids.join(); }; + function setDirty() { + angularHelper.getCurrentForm($scope).$setDirty(); + } + function reloadUpdatedMediaItems(updatedMediaNodes) { // because the images can be edited through the media picker we need to // reload. We only reload the images that is already picked but has been updated. @@ -152,6 +156,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl $scope.mediaItems.splice(index, 1); $scope.ids.splice(index, 1); sync(); + setDirty(); }; $scope.editItem = function (item) { @@ -215,6 +220,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl }); sync(); reloadUpdatedMediaItems(model.updatedMediaNodes); + setDirty(); }, close: function (model) { editorService.close(); @@ -233,6 +239,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl items: "li:not(.add-wrapper)", cancel: ".unsortable", update: function (e, ui) { + setDirty(); var r = []; // TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the From cec4376d199e617546292d726829c0a456c75655 Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Fri, 2 Aug 2019 21:34:22 +0100 Subject: [PATCH 11/19] Update GetPagedResultsByQuery to return id of the node the audit item relates to instead of table PK This was fixed previously in v7 - see https://github.com/umbraco/Umbraco-CMS/pull/3759 (cherry picked from commit 53c52abde5dbac3c61d2f7481340d28bd48b1780) --- .../Persistence/Repositories/Implement/AuditRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs index c25328b10c..0788594e3a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs @@ -174,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement totalRecords = page.TotalItems; var items = page.Items.Select( - dto => new AuditItem(dto.Id, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList(); + dto => new AuditItem(dto.NodeId, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList(); // map the DateStamp for (var i = 0; i < items.Count; i++) From 0e565ddbf83811138712e38614caf208bd2f452c Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 12 Aug 2019 21:52:00 +0200 Subject: [PATCH 12/19] Make sure treepicker URLs are displayed for the current editor culture (cherry picked from commit 438133ae1fdcaec26df913848ebdebe0fc6e123c) --- .../src/common/resources/entity.resource.js | 9 +++++++-- src/Umbraco.Web/Editors/EntityController.cs | 7 +++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 234ed588c5..e8bdb9efb3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -104,21 +104,26 @@ function entityResource($q, $http, umbRequestHelper) { * * @param {Int} id Id of node to return the public url to * @param {string} type Object type name + * @param {string} culture Culture * @returns {Promise} resourcePromise object containing the url. * */ - getUrl: function (id, type) { + getUrl: function (id, type, culture) { if (id === -1 || id === "-1") { return ""; } + if (!culture) { + culture = ""; + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetUrl", - [{ id: id }, {type: type }])), + [{ id: id }, {type: type }, {culture: culture }])), 'Failed to retrieve url for id:' + id); }, diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index b3edb308a2..f53251fb23 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -210,17 +210,20 @@ namespace Umbraco.Web.Editors /// /// Int id of the entity to fetch URL for /// The type of entity such as Document, Media, Member + /// The culture to fetch the URL for /// The URL or path to the item /// /// We are not restricting this with security because there is no sensitive data /// - public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) + public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type, string culture = null) { + culture = culture ?? ClientCulture(); + var returnUrl = string.Empty; if (type == UmbracoEntityTypes.Document) { - var foundUrl = UmbracoContext.Url(id); + var foundUrl = UmbracoContext.Url(id, culture); if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") { returnUrl = foundUrl; From ccc2a0e811717c334343ef52c0cb9c81f119f434 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 19 Aug 2019 16:01:03 +0200 Subject: [PATCH 13/19] Fix #5335 - Multiple Media Picker not available as macro parameter (cherry picked from commit 1f2c4e38acac5d06a50c1b4a03e897be1314aca5) --- src/Umbraco.Core/Constants-PropertyEditors.cs | 5 ++++ .../MultipleMediaPickerParameterEditor.cs | 27 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 3 files changed, 33 insertions(+) create mode 100644 src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index b48286f197..b96c651f1c 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -96,6 +96,11 @@ namespace Umbraco.Core /// public const string MediaPicker = "Umbraco.MediaPicker"; + /// + /// Multiple Media Picker. + /// + public const string MultipleMediaPicker = "Umbraco.MultipleMediaPicker"; + /// /// Member Picker. /// diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs new file mode 100644 index 0000000000..1208a5eecc --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs @@ -0,0 +1,27 @@ +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors.ParameterEditors +{ + /// + /// Represents a multiple media picker macro parameter editor. + /// + [DataEditor( + Constants.PropertyEditors.Aliases.MultipleMediaPicker, + EditorType.MacroParameter, + "Multiple Media Picker", + "mediapicker", + ValueType = ValueTypes.Text)] + public class MultipleMediaPickerParameterEditor : DataEditor + { + /// + /// Initializes a new instance of the class. + /// + public MultipleMediaPickerParameterEditor(ILogger logger) + : base(logger) + { + DefaultConfiguration.Add("multiPicker", "1"); + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2365017504..1769218f05 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -224,6 +224,7 @@ + From 08c3b6bff56e2239dd78cc45d18b585aa375342b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 21 Aug 2019 12:45:06 +0200 Subject: [PATCH 14/19] Fix unit tests (cherry picked from commit 82e11d7e0a62e22cf1b139155f6ea1e6c2f6ea71) --- src/Umbraco.Tests/Composing/TypeLoaderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 7459ae848b..9cd4f39c17 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -268,7 +268,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(38, types.Count()); + Assert.AreEqual(39, types.Count()); } /// From a2899b21b66b1bca9be193a03644596d6c94dc90 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 22 Aug 2019 20:36:26 +0200 Subject: [PATCH 15/19] Don't crash the linkpicker if only an anchor has been entered (cherry picked from commit 1089b4b9bb25e18140fc319e2cc0cfc228bf60ec) --- .../common/infiniteeditors/linkpicker/linkpicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index 5a0ab51fd0..ece95593b0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -93,7 +93,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", }); } - } else if ($scope.model.target.url.length) { + } else if ($scope.model.target.url && $scope.model.target.url.length) { // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? var indexOfAnchor = $scope.model.target.url.search(/(#|\?)/); From b396cc340cfdf0b1ea90ab3729a35758f435642c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 22 Aug 2019 22:06:57 +0200 Subject: [PATCH 16/19] Cherry pick - Added trck by $index to handle duplicate names in blueprints --- src/Umbraco.Web.UI.Client/src/views/content/create.html | 2 +- .../src/views/propertyeditors/listview/listview.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/create.html b/src/Umbraco.Web.UI.Client/src/views/content/create.html index b8303efade..cbedabba79 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -42,7 +42,7 @@