From fb4de621c003afc557faa4f2d3ef3f6eaf2de88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mads=20J=C3=B8rgensen?= Date: Wed, 16 Apr 2014 20:10:53 +0200 Subject: [PATCH 001/510] Prevalue editor added to textbox --- .../PropertyEditors/TextboxPropertyEditor.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs index 69bc1452cf..01cd2585f3 100644 --- a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs @@ -12,6 +12,19 @@ namespace Umbraco.Web.PropertyEditors { [PropertyEditor(Constants.PropertyEditors.TextboxAlias, "Textbox", "textbox", IsParameterEditor = true)] public class TextboxPropertyEditor : PropertyEditor - { + { + + + protected override PreValueEditor CreatePreValueEditor() + { + return new TextboxPreValueEditor(); + } + + internal class TextboxPreValueEditor : PreValueEditor + { + [PreValueField("Maximum allowed characters", "number")] + public bool IsRequired { get; set; } + } + } } From 756d554b47956973a55a39d8c247fe25c9ea9b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mads=20J=C3=B8rgensen?= Date: Wed, 16 Apr 2014 20:55:45 +0200 Subject: [PATCH 002/510] charlimit on textbox added --- .../textbox/textbox.controller.js | 26 +++++++++++++++++++ .../propertyeditors/textbox/textbox.html | 7 ++--- .../PropertyEditors/TextboxPropertyEditor.cs | 4 +-- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js new file mode 100644 index 0000000000..77052a70f5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -0,0 +1,26 @@ +function textboxController($rootScope, $scope, $log) { + $scope.model.maxlength = false; + if($scope.model.config.MaxChars) { + $scope.model.maxlength = true; + if($scope.model.value == undefined) { + $scope.model.count = ($scope.model.config.MaxChars * 1); + } else { + $scope.model.count = ($scope.model.config.MaxChars * 1) - $scope.model.value.length; + } + } + + $scope.model.change = function() { + if($scope.model.config.MaxChars) { + if($scope.model.value == undefined) { + $scope.model.count = ($scope.model.config.MaxChars * 1); + } else { + $scope.model.count = ($scope.model.config.MaxChars * 1) - $scope.model.value.length; + } + if($scope.model.count < 0) { + $scope.model.value = $scope.model.value.substring(0, ($scope.model.config.MaxChars * 1)); + $scope.model.count = 0; + } + } + } +} +angular.module('umbraco').controller("Umbraco.PropertyEditors.textboxController", textboxController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 7df7e9fded..1e23b36718 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -1,9 +1,10 @@ -
+
- + ng-required="model.validation.mandatory" + ng-keyup="model.change()" /> Required +
{{model.count}} characters left
diff --git a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs index 01cd2585f3..85b4155f57 100644 --- a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs @@ -22,8 +22,8 @@ namespace Umbraco.Web.PropertyEditors internal class TextboxPreValueEditor : PreValueEditor { - [PreValueField("Maximum allowed characters", "number")] - public bool IsRequired { get; set; } + [PreValueField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")] + public bool MaxChars { get; set; } } } From 53928c02f8b987acbe6556db66eff7ab177e0254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mads=20J=C3=B8rgensen?= Date: Wed, 16 Apr 2014 22:20:55 +0200 Subject: [PATCH 003/510] Textarea made work --- .../textarea/textarea.controller.js | 26 +++++++++++++++++++ .../propertyeditors/textarea/textarea.html | 10 +++++-- .../textbox/textbox.controller.js | 14 +++++----- .../propertyeditors/textbox/textbox.html | 5 +++- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 3 +++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 3 +++ .../umbraco/config/lang/en_us.xml | 3 +++ .../PropertyEditors/TextAreaPropertyEditor.cs | 10 +++++++ 8 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js new file mode 100644 index 0000000000..2d4e114dcf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js @@ -0,0 +1,26 @@ +function textAreaController($rootScope, $scope, $log) { + $scope.model.maxlength = false; + if($scope.model.config.maxChars) { + $scope.model.maxlength = true; + if($scope.model.value == undefined) { + $scope.model.count = ($scope.model.config.maxChars * 1); + } else { + $scope.model.count = ($scope.model.config.maxChars * 1) - $scope.model.value.length; + } + } + + $scope.model.change = function() { + if($scope.model.config.maxChars) { + if($scope.model.value == undefined) { + $scope.model.count = ($scope.model.config.maxChars * 1); + } else { + $scope.model.count = ($scope.model.config.maxChars * 1) - $scope.model.value.length; + } + if($scope.model.count < 0) { + $scope.model.value = $scope.model.value.substring(0, ($scope.model.config.maxChars * 1)); + $scope.model.count = 0; + } + } + } +} +angular.module('umbraco').controller("Umbraco.PropertyEditors.textAreaController", textAreaController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index 8601155bda..92735db301 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -1,2 +1,8 @@ - - \ No newline at end of file +
+ + +
+ {{model.count}} + characters left +
+
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js index 77052a70f5..03f7510112 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -1,23 +1,23 @@ function textboxController($rootScope, $scope, $log) { $scope.model.maxlength = false; - if($scope.model.config.MaxChars) { + if($scope.model.config.maxChars) { $scope.model.maxlength = true; if($scope.model.value == undefined) { - $scope.model.count = ($scope.model.config.MaxChars * 1); + $scope.model.count = ($scope.model.config.maxChars * 1); } else { - $scope.model.count = ($scope.model.config.MaxChars * 1) - $scope.model.value.length; + $scope.model.count = ($scope.model.config.maxChars * 1) - $scope.model.value.length; } } $scope.model.change = function() { - if($scope.model.config.MaxChars) { + if($scope.model.config.maxChars) { if($scope.model.value == undefined) { - $scope.model.count = ($scope.model.config.MaxChars * 1); + $scope.model.count = ($scope.model.config.maxChars * 1); } else { - $scope.model.count = ($scope.model.config.MaxChars * 1) - $scope.model.value.length; + $scope.model.count = ($scope.model.config.maxChars * 1) - $scope.model.value.length; } if($scope.model.count < 0) { - $scope.model.value = $scope.model.value.substring(0, ($scope.model.config.MaxChars * 1)); + $scope.model.value = $scope.model.value.substring(0, ($scope.model.config.maxChars * 1)); $scope.model.count = 0; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 1e23b36718..699c677621 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -6,5 +6,8 @@ ng-keyup="model.change()" /> Required -
{{model.count}} characters left
+
+ {{model.count}} + characters left +
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index c4b85332fa..6244e8f7e1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -801,4 +801,7 @@ Mange hilsner fra umbraco robotten Din historik Session udløber + + Karakterer tilbage + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 5d57f57f42..a00131baba 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -985,4 +985,7 @@ To manage your website, simply open the umbraco back office and start adding con Your recent history Session expires in + + characters left + \ No newline at end of file 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 54e4918126..758ae1efaf 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -998,4 +998,7 @@ To manage your website, simply open the umbraco back office and start adding con Your recent history Session expires in + + characters left + diff --git a/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs index e92a78dea8..9d5052c7e2 100644 --- a/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs @@ -6,5 +6,15 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.TextboxMultipleAlias, "Textarea", "textarea", IsParameterEditor = true)] public class TextAreaPropertyEditor : PropertyEditor { + protected override PreValueEditor CreatePreValueEditor() + { + return new TextAreaPreValueEditor(); + } + + internal class TextAreaPreValueEditor : PreValueEditor + { + [PreValueField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")] + public bool MaxChars { get; set; } + } } } \ No newline at end of file From b199f4072c551058c480bd0df40605017db8d9e2 Mon Sep 17 00:00:00 2001 From: rodyvansambeek Date: Wed, 17 Feb 2016 09:46:49 +0100 Subject: [PATCH 004/510] Improved and added dutch translation Added and changed some dutch translations, especially for members and subnodes (in a listview). The term "kinderen" only relates to children (persons), not items or nodes. --- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 9c8f587ca2..24e6d24c35 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -163,13 +163,17 @@ Lid van groep(en) Geen lid van groep(en) - Kinderen + Subitems Doel Klik om te uploaden Plaats je bestanden hier... + + Nieuw lid aanmaken + Alle Leden + Waar wil je de nieuwe %0% aanmaken? Aanmaken onder @@ -532,7 +536,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je De huidige node is niet toegestaan onder de geselecteerde node vanwege het node type De huidige node kan niet naar een van zijn subpagina’s worden verplaatst. De huidige node kan niet worden gebruikt op root-niveau - Deze actie is niet toegestaan omdat je onvoldoende rechten hebt op 1 of meer kinderen. + Deze actie is niet toegestaan omdat je onvoldoende rechten hebt op 1 of meer subitems. Relateer gekopieerde items aan het origineel @@ -620,7 +624,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Plakken, en verwijder de opmaak (aanbevolen) - Geavanceerd: Beveilig door de Member Groups te seecteren die toegang hebben op de pagina + Geavanceerd: Beveilig door de Member Groups te selecteren die toegang hebben op de pagina gebruik makend van Umbraco's member groups.]]> role-based authentication.]]> Error Pagina @@ -650,14 +654,14 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je %0% kon niet worden gepubliceerd doordat een 3rd party extensie het heeft geannuleerd. ]]> - Inclusief ongepubliceerde kinderen + Inclusief ongepubliceerde subitems Publicatie in uitvoering - even geduld... %0% van %1% pagina’s zijn gepubliceerd... %0% is gepubliceerd %0% en onderliggende pagina’s zijn gepubliceerd - Publiceer %0% en alle kinderen + Publiceer %0% en alle subitems ok om %0% te publiceren en de wijzigingen zichtbaar te maken voor bezoekers.

- Je kunt deze pagina publiceren en alle onderliggende sub-pagina's door publiceer alle kinderen aan te vinken hieronder. + Je kunt deze pagina publiceren en alle onderliggende sub-pagina's door publiceer alle subitems aan te vinken hieronder. ]]>
@@ -986,10 +990,10 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Ongeldig huidig wachtwoord Beide wachtwoorden waren niet hetzelfde. Probeer opnieuw! Beide wachtwoorden zijn niet hetzelfde! - Vervang rechten op de subnodes + Vervang rechten op de subitems U bent momenteel rechten aan het aanpassen voor volgende pagina's: Selecteer pagina's om hun rechten aan te passen - Doorzoek alle subnodes + Doorzoek alle subitems Startnode in Content Gebruikersnaam Gebruikersrechten From 27f121f25cc6d6f8003e9760b3b8326baca4b35d Mon Sep 17 00:00:00 2001 From: AndyButland Date: Wed, 21 Sep 2016 15:15:44 +0200 Subject: [PATCH 005/510] Fixes debug setting health check to correctly indicate no issue if config setting is missing --- .../Checks/Config/AbstractConfigCheck.cs | 22 +++++++++++++++---- .../Checks/Config/CompilationDebugCheck.cs | 5 +++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs index 001bd4e3e8..2cec88d616 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs @@ -43,6 +43,14 @@ namespace Umbraco.Web.HealthCheck.Checks.Config /// public abstract ValueComparisonType ValueComparisonType { get; } + /// + /// Gets the flag indicating if the check is considered successful if the config value is missing (defaults to false - an error - if missing) + /// + public virtual bool ValidIfConfigMissing + { + get { return false; } + } + protected AbstractConfigCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) { _textService = healthCheckContext.ApplicationContext.Services.TextService; @@ -132,11 +140,18 @@ namespace Umbraco.Web.HealthCheck.Checks.Config public override IEnumerable GetStatus() { + var successMessage = string.Format(CheckSuccessMessage, FileName, XPath, Values, CurrentValue); + var configValue = _configurationService.GetConfigurationValue(); if (configValue.Success == false) { - var message = configValue.Result; - return new[] { new HealthCheckStatus(message) { ResultType = StatusResultType.Error } }; + if (ValidIfConfigMissing) + { + return new[] { new HealthCheckStatus(successMessage) { ResultType = StatusResultType.Success } }; + } + + var errorMessage = configValue.Result; + return new[] { new HealthCheckStatus(errorMessage) { ResultType = StatusResultType.Error } }; } CurrentValue = configValue.Result; @@ -144,8 +159,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config var valueFound = Values.Any(value => string.Equals(CurrentValue, value.Value, StringComparison.InvariantCultureIgnoreCase)); if (ValueComparisonType == ValueComparisonType.ShouldEqual && valueFound || ValueComparisonType == ValueComparisonType.ShouldNotEqual && valueFound == false) { - var message = string.Format(CheckSuccessMessage, FileName, XPath, Values, CurrentValue); - return new[] { new HealthCheckStatus(message) { ResultType = StatusResultType.Success } }; + return new[] { new HealthCheckStatus(successMessage) { ResultType = StatusResultType.Success } }; } // Declare the action for rectifying the config value diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs index 989046b464..6612b77b26 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs @@ -30,6 +30,11 @@ namespace Umbraco.Web.HealthCheck.Checks.Config get { return ValueComparisonType.ShouldEqual; } } + public override bool ValidIfConfigMissing + { + get { return true; } + } + public override IEnumerable Values { get From cbb36a900a08cb5dec2df6a923aafd2a694dbf04 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Wed, 26 Oct 2016 22:38:54 +0200 Subject: [PATCH 006/510] Management of user groups with assignment of users. Migration for creation of user group tables and data amends for moving from permissions on users to those on groups. --- .../Models/Membership/EntityPermission.cs | 16 +- src/Umbraco.Core/Models/Membership/IUser.cs | 21 +- .../Models/Membership/IUserGroup.cs | 13 + .../Models/Membership/IUserType.cs | 24 +- .../Models/Membership/IUserTypeGroupBase.cs | 26 ++ src/Umbraco.Core/Models/Membership/User.cs | 41 +- .../Models/Membership/UserGroup.cs | 36 +- .../Models/Membership/UserType.cs | 62 +-- .../Models/Membership/UserTypeGroupBase.cs | 68 ++++ .../Models/Rdbms/User2UserGroupDto.cs | 19 + .../Models/Rdbms/UserGroup2AppDto.cs | 19 + .../Rdbms/UserGroup2NodePermissionDto.cs | 22 ++ src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs | 32 ++ .../Persistence/Factories/UserFactory.cs | 54 +-- .../Persistence/Factories/UserGroupFactory.cs | 77 ++++ .../Persistence/Mappers/UserGroupMapper.cs | 41 ++ .../Initial/DatabaseSchemaCreation.cs | 10 +- .../AddUserGroupTables.cs | 138 +++++++ .../Relators/UserGroupSectionRelator.cs | 50 +++ .../Interfaces/IUserGroupRepository.cs | 52 +++ .../Interfaces/IUserRepository.cs | 23 +- .../Repositories/UserGroupRepository.cs | 263 +++++++++++++ .../Repositories/UserRepository.cs | 128 +++++- .../Persistence/RepositoryFactory.cs | 8 + src/Umbraco.Core/Services/IUserService.cs | 95 ++++- src/Umbraco.Core/Services/UserService.cs | 372 +++++++++++++++++- src/Umbraco.Core/Umbraco.Core.csproj | 13 + .../Persistence/BaseTableByTableTest.cs | 126 +++--- .../Services/SectionServiceTests.cs | 102 +++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web.UI.Client/src/less/forms.less | 5 + src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 + .../config/trees.Release.config | 5 +- src/Umbraco.Web.UI/config/trees.config | 5 +- .../umbraco/config/create/UI.Release.xml | 20 +- .../umbraco/config/create/UI.xml | 8 + src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 8 +- .../umbraco/config/lang/en_us.xml | 4 + .../umbraco/users/EditUserGroup.aspx | 52 +++ src/Umbraco.Web/Security/WebSecurity.cs | 4 +- src/Umbraco.Web/Umbraco.Web.csproj | 12 + .../umbraco/Trees/UserGroups.cs | 50 +++ .../umbraco/Trees/UserTypes.cs | 8 - .../umbraco/users/EditUser.aspx.cs | 108 ++++- .../umbraco/users/EditUserGroup.aspx | 52 +++ .../umbraco/users/EditUserGroup.aspx.cs | 175 ++++++++ .../users/EditUserGroup.aspx.designer.cs | 195 +++++++++ .../umbraco/users/EditUserGroupsBase.cs | 39 ++ src/umbraco.businesslogic/User.cs | 62 +-- src/umbraco.businesslogic/UserGroup.cs | 309 +++++++++++++++ .../umbraco.businesslogic.csproj | 1 + 51 files changed, 2812 insertions(+), 263 deletions(-) create mode 100644 src/Umbraco.Core/Models/Membership/IUserGroup.cs create mode 100644 src/Umbraco.Core/Models/Membership/IUserTypeGroupBase.cs create mode 100644 src/Umbraco.Core/Models/Membership/UserTypeGroupBase.cs create mode 100644 src/Umbraco.Core/Models/Rdbms/User2UserGroupDto.cs create mode 100644 src/Umbraco.Core/Models/Rdbms/UserGroup2AppDto.cs create mode 100644 src/Umbraco.Core/Models/Rdbms/UserGroup2NodePermissionDto.cs create mode 100644 src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs create mode 100644 src/Umbraco.Core/Persistence/Relators/UserGroupSectionRelator.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs create mode 100644 src/Umbraco.Tests/Services/SectionServiceTests.cs create mode 100644 src/Umbraco.Web.UI/umbraco/users/EditUserGroup.aspx create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserGroups.cs create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx.cs create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx.designer.cs create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroupsBase.cs create mode 100644 src/umbraco.businesslogic/UserGroup.cs diff --git a/src/Umbraco.Core/Models/Membership/EntityPermission.cs b/src/Umbraco.Core/Models/Membership/EntityPermission.cs index 7ab1ddc817..520a9b8d37 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermission.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermission.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Linq; namespace Umbraco.Core.Models.Membership { @@ -21,6 +21,20 @@ namespace Umbraco.Core.Models.Membership /// The assigned permissions for the user/entity combo /// public string[] AssignedPermissions { get; private set; } + + /// + /// Adds additional permissions to an existing instance of + /// ensuring that only ones that aren't already assigned are added + /// + /// + public void AddAdditionalPermissions(string[] additionalPermissions) + { + var newPermissions = AssignedPermissions.ToList(); + newPermissions.AddRange(additionalPermissions); + AssignedPermissions = newPermissions + .Distinct() + .ToArray(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index f1f9c23971..095044c47e 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models.Membership { @@ -15,12 +14,28 @@ namespace Umbraco.Core.Models.Membership int StartContentId { get; set; } int StartMediaId { get; set; } string Language { get; set; } - + /// /// Gets/sets the user type for the user /// IUserType UserType { get; set; } + /// + /// Gets the groups that user is part of + /// + IEnumerable Groups { get; } + + /// + /// Indicates if the groups for a user have been loaded + /// + bool GroupsLoaded { get; } + + void RemoveGroup(IUserGroup group); + + void AddGroup(IUserGroup group); + + void SetGroupsLoaded(); + //TODO: This should be a private set /// /// The default permission set for the user @@ -31,7 +46,9 @@ namespace Umbraco.Core.Models.Membership IEnumerable DefaultPermissions { get; set; } IEnumerable AllowedSections { get; } + void RemoveAllowedSection(string sectionAlias); + void AddAllowedSection(string sectionAlias); /// diff --git a/src/Umbraco.Core/Models/Membership/IUserGroup.cs b/src/Umbraco.Core/Models/Membership/IUserGroup.cs new file mode 100644 index 0000000000..fc1af97912 --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/IUserGroup.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Membership +{ + public interface IUserGroup : IUserTypeGroupBase + { + IEnumerable AllowedSections { get; } + + void RemoveAllowedSection(string sectionAlias); + + void AddAllowedSection(string sectionAlias); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/IUserType.cs b/src/Umbraco.Core/Models/Membership/IUserType.cs index fe678afd2b..ccfdc729f5 100644 --- a/src/Umbraco.Core/Models/Membership/IUserType.cs +++ b/src/Umbraco.Core/Models/Membership/IUserType.cs @@ -1,28 +1,6 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; - namespace Umbraco.Core.Models.Membership { - - public interface IUserType : IAggregateRoot + public interface IUserType : IUserTypeGroupBase { - /// - /// The user type alias - /// - string Alias { get; set; } - - /// - /// The user type name - /// - string Name { get; set; } - - /// - /// The set of default permissions for the user type - /// - /// - /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. - /// - IEnumerable Permissions { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/IUserTypeGroupBase.cs b/src/Umbraco.Core/Models/Membership/IUserTypeGroupBase.cs new file mode 100644 index 0000000000..06769b1866 --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/IUserTypeGroupBase.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models.Membership +{ + public interface IUserTypeGroupBase : IAggregateRoot + { + /// + /// The alias + /// + string Alias { get; set; } + + /// + /// The name + /// + string Name { get; set; } + + /// + /// The set of default permissions + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. + /// + IEnumerable Permissions { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index cadda14508..6fad1a6f78 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -8,7 +8,6 @@ using System.Runtime.Serialization; using Umbraco.Core.Configuration; using Umbraco.Core.Models.EntityBase; - namespace Umbraco.Core.Models.Membership { /// @@ -29,6 +28,7 @@ namespace Umbraco.Core.Models.Membership _sectionCollection = new ObservableCollection(); _addedSections = new List(); _removedSections = new List(); + _groupCollection = new List(); _language = GlobalSettings.DefaultUILanguage; _sectionCollection.CollectionChanged += SectionCollectionChanged; _isApproved = true; @@ -58,6 +58,8 @@ namespace Umbraco.Core.Models.Membership private List _addedSections; private List _removedSections; private ObservableCollection _sectionCollection; + private List _groupCollection; + private bool _groupsLoaded; private int _sessionTimeout; private int _startContentId; private int _startMediaId; @@ -324,6 +326,41 @@ namespace Umbraco.Core.Models.Membership } } + /// + /// Gets or sets the groups that user is part of + /// + [DataMember] + public IEnumerable Groups + { + get { return _groupCollection; } + } + + /// + /// Indicates if the groups for a user have been loaded + /// + public bool GroupsLoaded { get { return _groupsLoaded; } } + + public void RemoveGroup(IUserGroup group) + { + if (_groupCollection.Select(x => x.Id).Contains(group.Id)) + { + _groupCollection.Remove(group); + } + } + + public void AddGroup(IUserGroup group) + { + if (_groupCollection.Select(x => x.Id).Contains(group.Id) == false) + { + _groupCollection.Add(group); + } + } + + public void SetGroupsLoaded() + { + _groupsLoaded = true; + } + #endregion /// @@ -365,7 +402,6 @@ namespace Umbraco.Core.Models.Membership { _removedSections.Add(item); } - } } @@ -378,6 +414,7 @@ namespace Umbraco.Core.Models.Membership clone._addedSections = new List(); clone._removedSections = new List(); clone._sectionCollection = new ObservableCollection(_sectionCollection.ToList()); + clone._groupCollection = new List(_groupCollection.ToList()); clone._defaultPermissions = new List(_defaultPermissions.ToList()); //re-create the event handler clone._sectionCollection.CollectionChanged += clone.SectionCollectionChanged; diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index b0f10d9ff1..403e40c8df 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -1,20 +1,42 @@ using System; +using System.Collections.Generic; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models.Membership { /// /// Represents a Group for a Backoffice User /// - /// - /// Should be internal until a proper user/membership implementation - /// is part of the roadmap. - /// [Serializable] [DataContract(IsReference = true)] - internal class UserGroup : Entity + internal class UserGroup : UserTypeGroupBase, IUserGroup { - //Add UserCollection ? + private List _sectionCollection; + + public UserGroup() + { + _sectionCollection = new List(); + } + + public IEnumerable AllowedSections + { + get { return _sectionCollection; } + } + + public void RemoveAllowedSection(string sectionAlias) + { + if (_sectionCollection.Contains(sectionAlias)) + { + _sectionCollection.Remove(sectionAlias); + } + } + + public void AddAllowedSection(string sectionAlias) + { + if (_sectionCollection.Contains(sectionAlias) == false) + { + _sectionCollection.Add(sectionAlias); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/UserType.cs b/src/Umbraco.Core/Models/Membership/UserType.cs index 01183e9d9d..92e0032bd6 100644 --- a/src/Umbraco.Core/Models/Membership/UserType.cs +++ b/src/Umbraco.Core/Models/Membership/UserType.cs @@ -1,72 +1,14 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Strings; namespace Umbraco.Core.Models.Membership { /// - /// Represents the Type for a Backoffice User + /// Represents the Type for a back-office User /// [Serializable] [DataContract(IsReference = true)] - internal class UserType : Entity, IUserType + internal class UserType : UserTypeGroupBase, IUserType { - private string _alias; - private string _name; - private IEnumerable _permissions; - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); - } - - [DataMember] - public string Alias - { - get { return _alias; } - set - { - SetPropertyValueAndDetectChanges( - value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), - ref _alias, - Ps.Value.AliasSelector); - } - } - - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// The set of default permissions for the user type - /// - /// - /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. - /// - [DataMember] - public IEnumerable Permissions - { - get { return _permissions; } - set - { - SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode())); - } - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/UserTypeGroupBase.cs b/src/Umbraco.Core/Models/Membership/UserTypeGroupBase.cs new file mode 100644 index 0000000000..b858747eb1 --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/UserTypeGroupBase.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Strings; + +namespace Umbraco.Core.Models.Membership +{ + /// + /// Base class representing common fields for back-office user types and groups + /// + internal abstract class UserTypeGroupBase : Entity + { + private string _alias; + private string _name; + private IEnumerable _permissions; + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); + } + + [DataMember] + public string Alias + { + get { return _alias; } + set + { + SetPropertyValueAndDetectChanges( + value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias, + Ps.Value.AliasSelector); + } + } + + [DataMember] + public string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + /// + /// The set of default permissions for the user type + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. + /// + [DataMember] + public IEnumerable Permissions + { + get { return _permissions; } + set + { + SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, + //Custom comparer for enumerable + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode())); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/User2UserGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/User2UserGroupDto.cs new file mode 100644 index 0000000000..afdbf661b5 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/User2UserGroupDto.cs @@ -0,0 +1,19 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoUser2UserGroup")] + [ExplicitColumns] + internal class User2UserGroupDto + { + [Column("userId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_user2userGroup", OnColumns = "userId, userGroupId")] + [ForeignKey(typeof(UserDto))] + public int UserId { get; set; } + + [Column("userGroupId")] + [ForeignKey(typeof(UserGroupDto))] + public int UserGroupId { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroup2AppDto.cs b/src/Umbraco.Core/Models/Rdbms/UserGroup2AppDto.cs new file mode 100644 index 0000000000..14fc60e197 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/UserGroup2AppDto.cs @@ -0,0 +1,19 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoUserGroup2App")] + [ExplicitColumns] + internal class UserGroup2AppDto + { + [Column("userGroupId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_userGroup2App", OnColumns = "userGroupId, app")] + [ForeignKey(typeof(UserGroupDto))] + public int UserGroupId { get; set; } + + [Column("app")] + [Length(50)] + public string AppAlias { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroup2NodePermissionDto.cs b/src/Umbraco.Core/Models/Rdbms/UserGroup2NodePermissionDto.cs new file mode 100644 index 0000000000..657aad2f31 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/UserGroup2NodePermissionDto.cs @@ -0,0 +1,22 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoUserGroup2NodePermission")] + [ExplicitColumns] + internal class UserGroup2NodePermissionDto + { + [Column("userGroupId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_umbracoUserGroup2NodePermission", OnColumns = "userGroupId, nodeId, permission")] + [ForeignKey(typeof(UserGroupDto))] + public int UserGroupId { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(NodeDto))] + public int NodeId { get; set; } + + [Column("permission")] + public string Permission { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs new file mode 100644 index 0000000000..dd1023b68a --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoUserGroup")] + [PrimaryKey("id")] + [ExplicitColumns] + internal class UserGroupDto + { + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = 5)] + public int Id { get; set; } + + [Column("userGroupAlias")] + [Length(200)] + public string Alias { get; set; } + + [Column("userGroupName")] + [Length(200)] + public string Name { get; set; } + + [Column("userGroupDefaultPermissions")] + [Length(50)] + [NullSetting(NullSetting = NullSettings.Null)] + public string DefaultPermissions { get; set; } + + [ResultColumn] + public List UserGroup2AppDtos { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 0a13c82447..322e8b5b65 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; @@ -44,9 +43,12 @@ namespace Umbraco.Core.Persistence.Factories user.LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue; user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue; - foreach (var app in dto.User2AppDtos) + if (dto.User2AppDtos != null) { - user.AddAllowedSection(app.AppAlias); + foreach (var app in dto.User2AppDtos) + { + user.AddAllowedSection(app.AppAlias); + } } //on initial construction we don't want to have dirty properties tracked @@ -64,41 +66,43 @@ namespace Umbraco.Core.Persistence.Factories public UserDto BuildDto(IUser entity) { var dto = new UserDto - { - ContentStartId = entity.StartContentId, - MediaStartId = entity.StartMediaId, - Disabled = entity.IsApproved == false, - Email = entity.Email, - Login = entity.Username, - NoConsole = entity.IsLockedOut, - Password = entity.RawPasswordValue, - UserLanguage = entity.Language, - UserName = entity.Name, - Type = short.Parse(entity.UserType.Id.ToString(CultureInfo.InvariantCulture)), - User2AppDtos = new List(), - SecurityStampToken = entity.SecurityStamp, - FailedLoginAttempts = entity.FailedPasswordAttempts, - LastLockoutDate = entity.LastLockoutDate == DateTime.MinValue ? (DateTime?)null : entity.LastLockoutDate, - LastLoginDate = entity.LastLoginDate == DateTime.MinValue ? (DateTime?)null : entity.LastLoginDate, - LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, - }; + { + ContentStartId = entity.StartContentId, + MediaStartId = entity.StartMediaId, + Disabled = entity.IsApproved == false, + Email = entity.Email, + Login = entity.Username, + NoConsole = entity.IsLockedOut, + Password = entity.RawPasswordValue, + UserLanguage = entity.Language, + UserName = entity.Name, + Type = short.Parse(entity.UserType.Id.ToString(CultureInfo.InvariantCulture)), + User2AppDtos = new List(), + SecurityStampToken = entity.SecurityStamp, + FailedLoginAttempts = entity.FailedPasswordAttempts, + LastLockoutDate = entity.LastLockoutDate == DateTime.MinValue ? (DateTime?)null : entity.LastLockoutDate, + LastLoginDate = entity.LastLoginDate == DateTime.MinValue ? (DateTime?)null : entity.LastLoginDate, + LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, + }; foreach (var app in entity.AllowedSections) { var appDto = new User2AppDto - { - AppAlias = app - }; + { + AppAlias = app + }; if (entity.HasIdentity) { - appDto.UserId = (int) entity.Id; + appDto.UserId = entity.Id; } dto.User2AppDtos.Add(appDto); } if (entity.HasIdentity) + { dto.Id = entity.Id.SafeCast(); + } return dto; } diff --git a/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs new file mode 100644 index 0000000000..39a4d13f7f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class UserGroupFactory + { + #region Implementation of IEntityFactory + + public IUserGroup BuildEntity(UserGroupDto dto) + { + var userGroup = new UserGroup(); + + try + { + userGroup.DisableChangeTracking(); + + userGroup.Alias = dto.Alias; + userGroup.Id = dto.Id; + userGroup.Name = dto.Name; + userGroup.Permissions = dto.DefaultPermissions.IsNullOrWhiteSpace() + ? Enumerable.Empty() + : dto.DefaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); + + if (dto.UserGroup2AppDtos != null) + { + foreach (var app in dto.UserGroup2AppDtos) + { + userGroup.AddAllowedSection(app.AppAlias); + } + } + + userGroup.ResetDirtyProperties(false); + return userGroup; + } + finally + { + userGroup.EnableChangeTracking(); + } + } + + public UserGroupDto BuildDto(IUserGroup entity) + { + var dto = new UserGroupDto + { + Alias = entity.Alias, + DefaultPermissions = entity.Permissions == null ? "" : string.Join("", entity.Permissions), + Name = entity.Name, + UserGroup2AppDtos = new List(), + }; + + foreach (var app in entity.AllowedSections) + { + var appDto = new UserGroup2AppDto + { + AppAlias = app + }; + if (entity.HasIdentity) + { + appDto.UserGroupId = entity.Id; + } + + dto.UserGroup2AppDtos.Add(appDto); + } + + if (entity.HasIdentity) + dto.Id = short.Parse(entity.Id.ToString()); + + return dto; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs new file mode 100644 index 0000000000..69386063db --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs @@ -0,0 +1,41 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(IUserGroup))] + [MapperFor(typeof(UserGroup))] + public sealed class UserGroupMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it + // otherwise that would fail because there is no public constructor. + public UserGroupMapper() + { + BuildMap(); + } + + #region Overrides of BaseMapper + + internal override ConcurrentDictionary PropertyInfoCache + { + get { return PropertyInfoCacheInstance; } + } + + internal override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.Name, dto => dto.Name); + CacheMap(src => src.Permissions, dto => dto.DefaultPermissions); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 423c847c47..6c06d1f06c 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -73,9 +73,9 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {34, typeof (TaskDto)}, {35, typeof (ContentType2ContentTypeDto)}, {36, typeof (ContentTypeAllowedContentTypeDto)}, - {37, typeof (User2AppDto)}, + // Removed in 7.6 {37, typeof (User2AppDto)}, {38, typeof (User2NodeNotifyDto)}, - {39, typeof (User2NodePermissionDto)}, + // Removed in 7.6 {39, typeof (User2NodePermissionDto)}, {40, typeof (ServerRegistrationDto)}, {41, typeof (AccessDto)}, {42, typeof (AccessRuleDto)}, @@ -84,7 +84,11 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {45, typeof (MigrationDto)}, {46, typeof (UmbracoDeployChecksumDto)}, {47, typeof (UmbracoDeployDependencyDto)}, - {48, typeof (RedirectUrlDto) } + {48, typeof (RedirectUrlDto) }, + {49, typeof (UserGroupDto) }, + {50, typeof (User2UserGroupDto) }, + {51, typeof (UserGroup2NodePermissionDto) }, + {52, typeof (UserGroup2AppDto) }, }; #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs new file mode 100644 index 0000000000..ac5a121b9c --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs @@ -0,0 +1,138 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; +using System.Data; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + [Migration("7.6.0", 100, GlobalSettings.UmbracoMigrationName)] + public class AddUserGroupTables : MigrationBase + { + public AddUserGroupTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + AddNewTables(tables); + MigratePermissionsData(); + DeleteOldTables(tables); + } + + private void AddNewTables(string[] tables) + { + if (tables.InvariantContains("umbracoUserGroup") == false) + { + Create.Table("umbracoUserGroup") + .WithColumn("id").AsInt32().Identity().PrimaryKey("PK_umbracoUserGroup") + .WithColumn("userGroupAlias").AsString(200).NotNullable() + .WithColumn("userGroupName").AsString(200).NotNullable() + .WithColumn("userGroupDefaultPermissions").AsString(50).Nullable(); + } + + if (tables.InvariantContains("umbracoUser2UserGroup") == false) + { + Create.Table("umbracoUser2UserGroup") + .WithColumn("userId").AsInt32().NotNullable() + .WithColumn("userGroupId").AsInt32().NotNullable(); + Create.PrimaryKey("PK_user2userGroup") + .OnTable("umbracoUser2UserGroup") + .Columns(new[] {"userId", "userGroupId"}); + Create.ForeignKey("FK_umbracoUser2UserGroup_userId") + .FromTable("umbracoUser2UserGroup").ForeignColumn("userId") + .ToTable("umbracoUser").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); + Create.ForeignKey("FK_umbracoUser2UserGroup_userGroupId") + .FromTable("umbracoUser2UserGroup").ForeignColumn("userGroupId") + .ToTable("umbracoUserGroup").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); + } + + if (tables.InvariantContains("umbracoUserGroup2App") == false) + { + Create.Table("umbracoUserGroup2App") + .WithColumn("userGroupId").AsInt32().NotNullable() + .WithColumn("app").AsString(50).NotNullable(); + Create.PrimaryKey("PK_userGroup2App") + .OnTable("umbracoUserGroup2App") + .Columns(new[] {"userGroupId", "app"}); + Create.ForeignKey("FK_umbracoUserGroup2App_umbracoGroupUser_id") + .FromTable("umbracoUserGroup2App").ForeignColumn("userGroupId") + .ToTable("umbracoUserGroup").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); + } + + if (tables.InvariantContains("umbracoUserGroup2NodePermission") == false) + { + Create.Table("umbracoUserGroup2NodePermission") + .WithColumn("userGroupId").AsInt32().NotNullable() + .WithColumn("nodeId").AsInt32().NotNullable() + .WithColumn("permission").AsString(10).NotNullable(); + Create.PrimaryKey("PK_umbracoUserGroup2NodePermission") + .OnTable("umbracoUserGroup2NodePermission") + .Columns(new[] {"userGroupId", "nodeId", "permission"}); + Create.ForeignKey("FK_umbracoUserGroup2NodePermission_umbracoNode_id") + .FromTable("umbracoUserGroup2NodePermission").ForeignColumn("nodeId") + .ToTable("umbracoNode").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); + Create.ForeignKey("FK_umbracoUserGroup2NodePermission_umbracoUserGroup_id") + .FromTable("umbracoUserGroup2NodePermission").ForeignColumn("userGroupId") + .ToTable("umbracoUserGroup").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); + } + } + + private void MigratePermissionsData() + { + // TODO: review for MySQL and CE (tested only on SQL Express) + + // Create user group records for all non-admin users that have specific permissions set + Execute.Sql(@"INSERT INTO umbracoUserGroup(userGroupAlias, userGroupName) + SELECT userName + 'Group', 'Group for ' + userName + FROM umbracoUser + WHERE (id IN ( + SELECT [user] + FROM umbracoUser2app + ) OR id IN ( + SELECT userid + FROM umbracoUser2NodePermission + )) + AND id > 0"); + + // Associate those groups with the users + Execute.Sql(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) + SELECT (SELECT id from umbracoUser WHERE userName + 'Group' = umbracoUserGroup.userGroupAlias), id + FROM umbracoUserGroup"); + + // Create node permissions on the groups + Execute.Sql(@"INSERT INTO umbracoUserGroup2NodePermission (userGroupId,nodeId,permission) + SELECT ug.id, nodeId, permission + FROM umbracoUserGroup ug + INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id + INNER JOIN umbracoUser u ON u.id = u2ug.userId + INNER JOIN umbracoUser2NodePermission u2np ON u2np.userId = u.id"); + + // Create app permissions on the groups + Execute.Sql(@"INSERT INTO umbracoUserGroup2app (userGroupId,app) + SELECT ug.id, app + FROM umbracoUserGroup ug + INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id + INNER JOIN umbracoUser u ON u.id = u2ug.userId + INNER JOIN umbracoUser2app u2a ON u2a.[user] = u.id"); + } + + private void DeleteOldTables(string[] tables) + { + if (tables.InvariantContains("umbracoUser2App")) + { + Delete.Table("umbracoUser2App"); + } + + if (tables.InvariantContains("umbracoUser2NodePermission")) + { + Delete.Table("umbracoUser2NodePermission"); + } + } + + public override void Down() + { } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/UserGroupSectionRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserGroupSectionRelator.cs new file mode 100644 index 0000000000..03c5bcd398 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Relators/UserGroupSectionRelator.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Relators +{ + internal class UserGroupSectionRelator + { + internal UserGroupDto Current; + + internal UserGroupDto Map(UserGroupDto a, UserGroup2AppDto p) + { + // Terminating call. Since we can return null from this function + // we need to be ready for PetaPoco to callback later with null + // parameters + if (a == null) + return Current; + + // Is this the same DictionaryItem as the current one we're processing + if (Current != null && Current.Id == a.Id) + { + if (p.AppAlias.IsNullOrWhiteSpace() == false) + { + // Yes, just add this UserGroup2AppDto to the current item's collection + Current.UserGroup2AppDtos.Add(p); + } + + // Return null to indicate we're not done with this User yet + return null; + } + + // This is a different user group to the current one, or this is the + // first time through and we don't have one yet + + // Save the current user group + var prev = Current; + + // Setup the new current user group + Current = a; + Current.UserGroup2AppDtos = new List(); + //this can be null since we are doing a left join + if (p.AppAlias.IsNullOrWhiteSpace() == false) + { + Current.UserGroup2AppDtos.Add(p); + } + + // Return the now populated previous user group (or null if first time through) + return prev; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs new file mode 100644 index 0000000000..e50d265228 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IUserGroupRepository : IRepositoryQueryable + { + /// + /// This is useful when an entire section is removed from config + /// + /// + IEnumerable GetGroupsAssignedToSection(string sectionAlias); + + /// + /// Removes all users from a group + /// + /// Id of group + void RemoveAllUsersFromGroup(int groupId); + + /// + /// Adds a set of users to a group + /// + /// Id of group + /// Ids of users + void AddUsersToGroup(int groupId, int[] userIds); + + /* + /// + /// Gets the group permissions for the specified entities + /// + /// Id of group + /// Array of entity Ids + IEnumerable GetPermissionsForEntities(int groupId, params int[] entityIds); + + /// + /// Replaces the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of + /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed. + void ReplaceGroupPermissions(int groupId, IEnumerable permissions, params int[] entityIds); + + /// + /// Assigns the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of + /// Specify the nodes to replace permissions for + void AssignGroupPermission(int groupId, char permission, params int[] entityIds); + */ + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index 542cceac40..5645cafa2a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -28,6 +28,25 @@ namespace Umbraco.Core.Persistence.Repositories /// IEnumerable GetUsersAssignedToSection(string sectionAlias); + /// + /// Gets all groups for a given user + /// + /// Id of user + /// An enumerable list of + IEnumerable GetGroupsForUser(int userId); + + /// + /// Gets a list of objects associated with a given group + /// + /// Id of group + IEnumerable GetAllInGroup(int groupId); + + /// + /// Gets a list of objects not associated with a given group + /// + /// Id of group + IEnumerable GetAllNotInGroup(int groupId); + /// /// Gets paged member results /// @@ -38,8 +57,8 @@ namespace Umbraco.Core.Persistence.Repositories /// /// IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy); - - + + /// /// Gets the user permissions for the specified entities /// diff --git a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs new file mode 100644 index 0000000000..ba47270379 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Relators; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represents the UserGroupRepository for doing CRUD operations for + /// + internal class UserGroupRepository : PetaPocoRepositoryBase, IUserGroupRepository + { + private readonly CacheHelper _cacheHelper; + + public UserGroupRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cacheHelper, logger, sqlSyntax) + { + _cacheHelper = cacheHelper; + } + + public IEnumerable GetGroupsAssignedToSection(string sectionAlias) + { + //Here we're building up a query that looks like this, a sub query is required because the resulting structure + // needs to still contain all of the section rows per user group. + + //SELECT * + //FROM [umbracoUserGroup] + //LEFT JOIN [umbracoUserGroup2App] + //ON [umbracoUserGroup].[id] = [umbracoUserGroup2App].[user] + //WHERE umbracoUserGroup.id IN (SELECT umbracoUserGroup.id + // FROM [umbracoUserGroup] + // LEFT JOIN [umbracoUserGroup2App] + // ON [umbracoUserGroup].[id] = [umbracoUserGroup2App].[user] + // WHERE umbracoUserGroup2App.app = 'content') + + var sql = GetBaseQuery(false); + var innerSql = GetBaseQuery("umbracoUserGroup.id"); + innerSql.Where("umbracoUserGroup2App.app = " + SqlSyntax.GetQuotedValue(sectionAlias)); + sql.Where(string.Format("umbracoUserGroup.id IN ({0})", innerSql.SQL)); + + return ConvertFromDtos(Database.Fetch(new UserGroupSectionRelator().Map, sql)); + } + + /// + /// Removes all users from a group + /// + /// Id of group + public void RemoveAllUsersFromGroup(int groupId) + { + Database.Delete("WHERE userGroupId = @GroupId", new { GroupId = groupId }); + } + + /// + /// Adds a set of users to a group + /// + /// Id of group + /// Ids of users + public void AddUsersToGroup(int groupId, int[] userIds) + { + foreach (var userId in userIds) + { + var dto = new User2UserGroupDto + { + UserGroupId = groupId, + UserId = userId, + }; + Database.Insert(dto); + } + } + + /* + /// + /// Gets the group permissions for the specified entities + /// + /// Id of group + /// Array of entity Ids + public IEnumerable GetGroupPermissionsForEntities(int groupId, params int[] entityIds) + { + var repo = new GroupPermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + return repo.GetGroupPermissionsForEntities(groupId, entityIds); + } + + /// + /// Replaces the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of + /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed. + public void ReplaceGroupPermissions(int groupId, IEnumerable permissions, params int[] entityIds) + { + var repo = new GroupPermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + repo.ReplaceGroupPermissions(groupId, permissions, entityIds); + } + + /// + /// Assigns the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of + /// Specify the nodes to replace permissions for + public void AssignGroupPermission(int groupId, char permission, params int[] entityIds) + { + var repo = new GroupPermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + repo.AssignGroupPermission(groupId, permission, entityIds); + } + */ + + #region Overrides of RepositoryBase + + protected override IUserGroup PerformGet(int id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + + var dto = Database.Fetch(new UserGroupSectionRelator().Map, sql).FirstOrDefault(); + + if (dto == null) + return null; + + var userGroupFactory = new UserGroupFactory(); + var userGroup = userGroupFactory.BuildEntity(dto); + return userGroup; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(false); + + if (ids.Any()) + { + sql.Where("umbracoUserGroup.id in (@ids)", new { ids = ids }); + } + else + { + sql.Where(x => x.Id >= 0); + } + + var dtos = Database.Fetch(new UserGroupSectionRelator().Map, sql); + return ConvertFromDtos(dtos); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var userGroupFactory = new UserGroupFactory(); + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + var dtos = Database.Fetch(new UserGroupSectionRelator().Map, sql); + return ConvertFromDtos(dtos); + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + if (isCount) + { + sql.Select("COUNT(*)").From(); + } + else + { + return GetBaseQuery("*"); + } + return sql; + } + + protected Sql GetBaseQuery(string columns) + { + var sql = new Sql(); + sql.Select(columns) + .From() + .LeftJoin() + .On(left => left.Id, right => right.UserGroupId); + + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoUserGroup.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM umbracoUserGroup2App WHERE userGroupId = @Id", + "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @Id", + "DELETE FROM umbracoUserGroup WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + + protected override void PersistNewItem(IUserGroup entity) + { + var userGroupFactory = new UserGroupFactory(); + var userGroupDto = userGroupFactory.BuildDto(entity); + + var id = Convert.ToInt32(Database.Insert(userGroupDto)); + entity.Id = id; + + PersistAllowedSections(entity); + } + + protected override void PersistUpdatedItem(IUserGroup entity) + { + var userGroupFactory = new UserGroupFactory(); + var userGroupDto = userGroupFactory.BuildDto(entity); + + Database.Update(userGroupDto); + + PersistAllowedSections(entity); + } + + private void PersistAllowedSections(IUserGroup entity) + { + var userGroup = (UserGroup)entity; + + // First delete all + Database.Delete("WHERE UserGroupId = @UserGroupId", + new { UserGroupId = userGroup.Id }); + + // Then re-add any associated with the group + foreach (var app in userGroup.AllowedSections) + { + var dto = new UserGroup2AppDto + { + UserGroupId = userGroup.Id, + AppAlias = app + }; + Database.Insert(dto); + } + } + + #endregion + + private IEnumerable ConvertFromDtos(IEnumerable dtos) + { + return dtos.Select(dto => + { + var userGroupFactory = new UserGroupFactory(); + return userGroupFactory.BuildEntity(dto); + }); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 4652ce47be..72d20ff660 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -48,10 +48,23 @@ namespace Umbraco.Core.Persistence.Repositories var userType = _userTypeRepository.Get(dto.Type); var userFactory = new UserFactory(userType); var user = userFactory.BuildEntity(dto); - + AssociateGroupsWithUser(user); return user; } + private void AssociateGroupsWithUser(IUser user) + { + if (user != null) + { + foreach (var group in GetGroupsForUser(user.Id)) + { + user.AddGroup(group); + } + + user.SetGroupsLoaded(); + } + } + protected override IEnumerable PerformGetAll(params int[] ids) { var sql = GetBaseQuery(false); @@ -62,7 +75,7 @@ namespace Umbraco.Core.Persistence.Repositories } return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)) - .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. + .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -74,8 +87,16 @@ namespace Umbraco.Core.Persistence.Repositories var dtos = Database.Fetch(new UserSectionRelator().Map, sql) .DistinctBy(x => x.Id); - return ConvertFromDtos(dtos) - .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. + var users = ConvertFromDtos(dtos) + .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. + + // If a single user found (most likely from a look-up by an alternate key like email or username) then populate the groups + if (users.Length == 1) + { + AssociateGroupsWithUser(users[0]); + } + + return users; } #endregion @@ -100,9 +121,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql(); sql.Select(columns) - .From() - .LeftJoin() - .On(left => left.Id, right => right.UserId); + .From() + .LeftJoin() + .On(left => left.Id, right => right.UserId); return sql; } @@ -225,9 +246,9 @@ namespace Umbraco.Core.Persistence.Repositories //now we need to delete any applications that have been removed foreach (var section in user.RemovedSections) { - //we need to manually delete thsi record because it has a composite key - Database.Delete("WHERE app=@Section AND " + SqlSyntax.GetQuotedColumnName("user") + "=@UserId", - new { Section = section, UserId = (int)user.Id }); + //we need to manually delete this record because it has a composite key + Database.Delete("WHERE app = @Section AND " + SqlSyntax.GetQuotedColumnName("user") + "= @UserId", + new { Section = section, UserId = user.Id }); } //for any that exist on the object, we need to determine if we need to update or insert @@ -248,7 +269,21 @@ namespace Umbraco.Core.Persistence.Repositories } } - + //update the groups + //first delete all + Database.Delete("WHERE UserId = @UserId", + new { UserId = user.Id }); + + //then re-add any associated with the user + foreach (var group in user.Groups) + { + var dto = new User2UserGroupDto + { + UserGroupId = group.Id, + UserId = user.Id + }; + Database.Insert(dto); + } } entity.ResetDirtyProperties(); @@ -304,6 +339,68 @@ namespace Umbraco.Core.Persistence.Repositories return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)); } + /// + /// Gets all groups for a given user + /// + /// Id of user + /// An enumerable list of + public IEnumerable GetGroupsForUser(int userId) + { + var sql = new Sql(); + sql.Select("*") + .From() + .LeftJoin() + .On(left => left.Id, right => right.UserGroupId); + + var innerSql = new Sql(); + innerSql.Select("umbracoUserGroup.id") + .From() + .LeftJoin() + .On(left => left.Id, right => right.UserGroupId) + .Where("umbracoUser2UserGroup.userId = " + userId); + + sql.Where(string.Format("umbracoUserGroup.id IN ({0})", innerSql.SQL)); + var dtos = Database.Fetch(new UserGroupSectionRelator().Map, sql); + return ConvertFromDtos(dtos); + } + + /// + /// Gets a list of objects associated with a given group + /// + /// Id of group + public IEnumerable GetAllInGroup(int groupId) + { + return GetAllInOrNotInGroup(groupId, true); + } + + /// + /// Gets a list of objects not associated with a given group + /// + /// Id of group + public IEnumerable GetAllNotInGroup(int groupId) + { + return GetAllInOrNotInGroup(groupId, false); + } + + private IEnumerable GetAllInOrNotInGroup(int groupId, bool include) + { + var sql = new Sql(); + sql.Select("*") + .From(); + + var innerSql = new Sql(); + innerSql.Select("umbracoUser.id") + .From() + .LeftJoin() + .On(left => left.Id, right => right.UserId) + .Where("umbracoUser2UserGroup.userGroupId = " + groupId); + + sql.Where(string.Format("umbracoUser.id {0} ({1})", + include ? "IN" : "NOT IN", + innerSql.SQL)); + return ConvertFromDtos(Database.Fetch(sql)); + } + /// /// Gets paged user results /// @@ -418,6 +515,15 @@ namespace Umbraco.Core.Persistence.Repositories }); } + private IEnumerable ConvertFromDtos(IEnumerable dtos) + { + return dtos.Select(dto => + { + var userGroupFactory = new UserGroupFactory(); + return userGroupFactory.BuildEntity(dto); + }); + } + /// /// Dispose disposable properties /// diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 85eb00ea87..b422dda40d 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -265,6 +265,14 @@ namespace Umbraco.Core.Persistence _logger, _sqlSyntax); } + public virtual IUserGroupRepository CreateUserGroupRepository(IDatabaseUnitOfWork uow) + { + return new UserGroupRepository( + uow, + _cacheHelper, + _logger, _sqlSyntax); + } + public virtual IUserRepository CreateUserRepository(IDatabaseUnitOfWork uow) { return new UserRepository( diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 7353f44cb8..2a3ebf9828 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -71,6 +71,30 @@ namespace Umbraco.Core.Services /// An enumerable list of IEnumerable GetPermissions(IUser user, params int[] nodeIds); + /// + /// Get permissions set for a user group and optional node ids + /// + /// Group to retrieve permissions for + /// Specifiying nothing will return all group permissions for all nodes + /// An enumerable list of + IEnumerable GetPermissions(IUserGroup group, params int[] nodeIds); + + /// + /// Gets the permissions for the provided user and path + /// + /// User to check permissions for + /// Path to check permissions for + /// String indicating permissions for provided user and path + string GetPermissionsForPath(IUser user, string path); + + /// + /// Gets the permissions for the provided group and path + /// + /// User to check permissions for + /// Path to check permissions for + /// String indicating permissions for provided user and path + string GetPermissionsForPath(IUserGroup group, string path); + /// /// Replaces the same permission set for a single user to any number of entities /// @@ -91,10 +115,24 @@ namespace Umbraco.Core.Services /// Specify the nodes to replace permissions for void AssignUserPermission(int userId, char permission, params int[] entityIds); + /// + /// Gets a list of objects associated with a given group + /// + /// Id of group + /// + IEnumerable GetAllInGroup(int groupId); + + /// + /// Gets a list of objects not associated with a given group + /// + /// Id of group + /// + IEnumerable GetAllNotInGroup(int groupId); + #region User types /// - /// Gets all UserTypes or thosed specified as parameters + /// Gets all UserTypes or those specified as parameters /// /// Optional Ids of UserTypes to retrieve /// An enumerable list of @@ -136,5 +174,60 @@ namespace Umbraco.Core.Services void DeleteUserType(IUserType userType); #endregion + + #region User groups + + /// + /// Gets all UserGroups or those specified as parameters + /// + /// Optional Ids of UserGroups to retrieve + /// An enumerable list of + IEnumerable GetAllUserGroups(params int[] ids); + + /// + /// Gets all UserGroups for a given user + /// + /// Id of user + /// An enumerable list of + IEnumerable GetGroupsForUser(int userId); + + /// + /// Gets a UserGroup by its Alias + /// + /// Alias of the UserGroup to retrieve + /// + IUserGroup GetUserGroupByAlias(string alias); + + /// + /// Gets a UserGroup by its Id + /// + /// Id of the UserGroup to retrieve + /// + IUserGroup GetUserGroupById(int id); + + /// + /// Gets a UserGroup by its Name + /// + /// Name of the UserGroup to retrieve + /// + IUserGroup GetUserGroupByName(string name); + + /// + /// Saves a UserGroup + /// + /// UserGroup to save + /// Flag for whether to update the list of users in the group + /// List of user Ids + /// Optional parameter to raise events. + /// Default is True otherwise set to False to not raise events + void SaveUserGroup(IUserGroup userGroup, bool updateUsers = false, int[] userIds = null, bool raiseEvents = true); + + /// + /// Deletes a UserGroup + /// + /// UserGroup to delete + void DeleteUserGroup(IUserGroup userGroup); + + #endregion } } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 8e984d1e5d..fcd8c45536 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -516,6 +516,34 @@ namespace Umbraco.Core.Services } } + /// + /// Gets a list of objects associated with a given group + /// + /// Id of group + /// + public IEnumerable GetAllInGroup(int groupId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateUserRepository(uow)) + { + return repository.GetAllInGroup(groupId); + } + } + + /// + /// Gets a list of objects not associated with a given group + /// + /// Id of group + /// + public IEnumerable GetAllNotInGroup(int groupId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateUserRepository(uow)) + { + return repository.GetAllNotInGroup(groupId); + } + } + #endregion #region Implementation of IUserService @@ -587,7 +615,7 @@ namespace Umbraco.Core.Services } /// - /// Gets all UserTypes or thosed specified as parameters + /// Gets all UserTypes or those specified as parameters /// /// Optional Ids of UserTypes to retrieve /// An enumerable list of @@ -687,6 +715,129 @@ namespace Umbraco.Core.Services DeletedUserType.RaiseEvent(new DeleteEventArgs(userType, false), this); } + /// + /// Gets all UserGroups or those specified as parameters + /// + /// Optional Ids of UserGroups to retrieve + /// An enumerable list of + public IEnumerable GetAllUserGroups(params int[] ids) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) + { + return repository.GetAll(ids).OrderBy(x => x.Name); + } + } + + /// + /// Gets all UserGroups for a given user + /// + /// Id of user + /// An enumerable list of + public IEnumerable GetGroupsForUser(int userId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateUserRepository(uow)) + { + return repository.GetGroupsForUser(userId); + } + } + + /// + /// Gets a UserGroup by its Alias + /// + /// Alias of the UserGroup to retrieve + /// + public IUserGroup GetUserGroupByAlias(string alias) + { + using (var repository = RepositoryFactory.CreateUserGroupRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Alias == alias); + var contents = repository.GetByQuery(query); + return contents.SingleOrDefault(); + } + } + + /// + /// Gets a UserGroup by its Id + /// + /// Id of the UserGroup to retrieve + /// + public IUserGroup GetUserGroupById(int id) + { + using (var repository = RepositoryFactory.CreateUserGroupRepository(UowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } + + /// + /// Gets a UserGroup by its Name + /// + /// Name of the UserGroup to retrieve + /// + public IUserGroup GetUserGroupByName(string name) + { + using (var repository = RepositoryFactory.CreateUserGroupRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Name == name); + var contents = repository.GetByQuery(query); + return contents.SingleOrDefault(); + } + } + + /// + /// Saves a UserGroup + /// + /// UserGroup to save + /// Flag for whether to update the list of users in the group + /// List of user Ids + /// Optional parameter to raise events. + /// Default is True otherwise set to False to not raise events + public void SaveUserGroup(IUserGroup userGroup, bool updateUsers = false, int[] userIds = null, bool raiseEvents = true) + { + if (raiseEvents) + { + if (SavingUserGroup.IsRaisedEventCancelled(new SaveEventArgs(userGroup), this)) + return; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) + { + repository.AddOrUpdate(userGroup); + uow.Commit(); + + if (updateUsers) + { + repository.RemoveAllUsersFromGroup(userGroup.Id); + repository.AddUsersToGroup(userGroup.Id, userIds); + } + } + + if (raiseEvents) + SavedUserGroup.RaiseEvent(new SaveEventArgs(userGroup, false), this); + } + + /// + /// Deletes a UserGroup + /// + /// UserGroup to delete + public void DeleteUserGroup(IUserGroup userGroup) + { + if (DeletingUserGroup.IsRaisedEventCancelled(new DeleteEventArgs(userGroup), this)) + return; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) + { + repository.Delete(userGroup); + uow.Commit(); + } + + DeletedUserGroup.RaiseEvent(new DeleteEventArgs(userGroup, false), this); + } + /// /// Removes a specific section from all users /// @@ -768,8 +919,205 @@ namespace Umbraco.Core.Services } } + /// + /// Get permissions set for a group and optional node ids + /// + /// Group to retrieve permissions for + /// Specifiying nothing will return all permissions for all nodes + /// An enumerable list of + public IEnumerable GetPermissions(IUserGroup group, params int[] nodeIds) + { + throw new NotImplementedException(); + } + + /// + /// Get permissions set for a user and optional node ids + /// + /// User repository + /// User to retrieve permissions for + /// Specifiying nothing will return all user permissions for all nodes + /// An enumerable list of + private IEnumerable GetPermissions(IUserRepository repository, IUser user, params int[] nodeIds) + { + var explicitPermissions = repository.GetUserPermissionsForEntities(user.Id, nodeIds); + var result = new List(explicitPermissions); + + // Save list of nodes that user has explicit permissions for - these take priority so we don't want + // to amend with any settings from groups (we'll take the special "null" action as not being defined + // as this is set if someone sets permissions on a user and then removes them) + var explicitlyDefinedEntityIds = result + .Where(IsNotNullActionPermission) + .Select(x => x.EntityId) + .ToArray(); + + // If no permissions are assigned to a particular node then, we will fill in those permissions with the user's defaults + var missingIds = nodeIds.Except(result.Select(x => x.EntityId)); + foreach (var id in missingIds) + { + result.Add( + new EntityPermission( + user.Id, + id, + user.DefaultPermissions.ToArray())); + } + + // Add or amend the existing permissions based on these + foreach (var group in user.Groups) + { + var groupPermissions = GetPermissions(group, nodeIds).ToList(); + foreach (var groupPermission in groupPermissions) + { + // Check if we already have this from explicitly set user permissions, if so, ignore it + if (explicitlyDefinedEntityIds.Contains(groupPermission.EntityId) == false) + { + // Add group permission, ensuring we keep a unique value for the entity Id in the list + AddOrAmendPermission(result, groupPermission); + } + } + } + + return result; + } + + private void AddOrAmendPermission(IList permissions, EntityPermission groupPermission) + { + var existingPermission = permissions + .SingleOrDefault(x => x.EntityId == groupPermission.EntityId); + if (existingPermission != null) + { + existingPermission.AddAdditionalPermissions(groupPermission.AssignedPermissions); + } + else + { + permissions.Add(groupPermission); + } + } + + /// + /// Gets the permissions for the provided user and path + /// + /// User to check permissions for + /// Path to check permissions for + /// String indicating permissions for provided user and path + public string GetPermissionsForPath(IUser user, string path) + { + var nodeId = GetNodeIdFromPath(path); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateUserRepository(uow)) + { + // Check for permissions directly assigned to the node for the user (if we have any, they take priority + // over anything defined on groups; we'll take the special "null" action as not being defined + // as this is set if someone sets permissions on a user and then removes them) + var permissions = GetPermissions(repository, user, nodeId) + .Where(IsNotNullActionPermission) + .ToList(); + var gotDirectlyAssignedUserPermissions = permissions.Any(); + + // If none found, and checking groups, get permissions from the groups + if (gotDirectlyAssignedUserPermissions == false) + { + foreach (var group in user.Groups) + { + var groupPermissions = GetPermissions(group, nodeId).ToList(); + permissions.AddRange(groupPermissions); + } + } + + // If none found directly on the user, get those defined on the user type + if (gotDirectlyAssignedUserPermissions == false) + { + var typePermissions = GetPermissions(repository, user, nodeId).ToList(); + permissions.AddRange(typePermissions); + } + + // Extract the net permissions from the path from the set of permissions found + string assignedPermissions; + if (TryGetAssignedPermissionsForNode(permissions, nodeId, out assignedPermissions)) + { + return assignedPermissions; + } + + // Exception to everything. If default cruds is empty and we're on root node; allow browse of root node + if (path == "-1") + { + return "F"; + } + + return string.Empty; + } + } + + /// + /// Gets the permissions for the provided group and path + /// + /// User to check permissions for + /// Path to check permissions for + /// String indicating permissions for provided user and path + public string GetPermissionsForPath(IUserGroup group, string path) + { + var nodeId = GetNodeIdFromPath(path); + + var permissions = GetPermissions(group).ToList(); + + string assignedPermissions; + TryGetAssignedPermissionsForNode(permissions, nodeId, out assignedPermissions); + return assignedPermissions; + } + + private static int GetNodeIdFromPath(string path) + { + return path.Contains(",") + ? int.Parse(path.Substring(path.LastIndexOf(",", StringComparison.Ordinal) + 1)) + : int.Parse(path); + } + + private static bool IsNotNullActionPermission(EntityPermission x) + { + const string NullActionChar = "-"; + return string.Join(string.Empty, x.AssignedPermissions) != NullActionChar; + } + + /// + /// Checks in a set of permissions associated with a user for those related to a given nodeId + /// + /// The set of permissions + /// The node Id + /// The permissions to return + /// True if permissions for the given path are found + public static bool TryGetAssignedPermissionsForNode(IList permissions, + int nodeId, + out string assignedPermissions) + { + if (permissions.Any(x => x.EntityId == nodeId)) + { + var found = permissions.First(x => x.EntityId == nodeId); + var assignedPermissionsArray = found.AssignedPermissions.ToList(); + + // Working with permissions assigned directly to a user AND to their groups, so maybe several per node + // and we need to get the most permissive set + foreach (var permission in permissions.Where(x => x.EntityId == nodeId).Skip(1)) + { + AddAdditionalPermissions(assignedPermissionsArray, permission.AssignedPermissions); + } + + assignedPermissions = string.Join("", assignedPermissionsArray); + return true; + } + + assignedPermissions = string.Empty; + return false; + } + + private static void AddAdditionalPermissions(List assignedPermissions, string[] additionalPermissions) + { + var permissionsToAdd = additionalPermissions + .Where(x => assignedPermissions.Contains(x) == false); + assignedPermissions.AddRange(permissionsToAdd); + } + #endregion - + /// /// Occurs before Save /// @@ -809,5 +1157,25 @@ namespace Umbraco.Core.Services /// Occurs after Delete /// public static event TypedEventHandler> DeletedUserType; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> SavingUserGroup; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> SavedUserGroup; + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> DeletingUserGroup; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> DeletedUserGroup; } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bf70f6afd6..555c76eb4c 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -385,6 +385,9 @@ + + + @@ -412,6 +415,10 @@ + + + + @@ -421,11 +428,13 @@ + + @@ -438,6 +447,7 @@ + @@ -474,6 +484,7 @@ + @@ -489,11 +500,13 @@ + + diff --git a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs index bed5582609..201fedfcad 100644 --- a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Persistence private DatabaseSchemaHelper _schemaHelper; public abstract Database Database { get; } - + protected abstract ISqlSyntaxProvider SqlSyntaxProvider { get; } protected DatabaseSchemaHelper DatabaseSchemaHelper @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Persistence public virtual void Initialize() { _logger = new Logger(new FileInfo(TestHelper.MapPathForTest("~/unit-test-log4net.config"))); - + TestHelper.InitializeContentDirectories(); string path = TestHelper.CurrentAssemblyDirectory; @@ -64,9 +64,9 @@ namespace Umbraco.Tests.Persistence new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(_logger), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, _logger), cacheHelper, _logger, evtMsgs), cacheHelper, new ProfilingLogger(_logger, Mock.Of())) - { - IsReady = true - }; + { + IsReady = true + }; Resolution.Freeze(); } @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Persistence //RepositoryResolver.Reset(); } - + [Test] public void Can_Create_umbracoNode_Table() @@ -135,7 +135,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoAppTree_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -147,7 +147,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsContentType2ContentType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -160,7 +160,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsContentTypeAllowedContentType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -174,7 +174,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsContentType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -187,7 +187,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsContentVersion_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -202,7 +202,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsContentXml_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -217,7 +217,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsDataType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -230,7 +230,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsDataTypePreValues_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -244,7 +244,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsDictionary_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -256,7 +256,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsLanguageText_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -270,7 +270,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsTemplate_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -283,7 +283,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsDocument_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -299,7 +299,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsDocumentType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -314,7 +314,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoDomains_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -327,7 +327,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoLanguage_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -339,7 +339,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoLog_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -351,7 +351,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsMacro_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -359,11 +359,11 @@ namespace Umbraco.Tests.Persistence //transaction.Complete(); } } - + [Test] public void Can_Create_cmsMember_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -378,7 +378,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsMember2MemberGroup_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -394,7 +394,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsMemberType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -408,7 +408,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsPreviewXml_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -424,7 +424,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsPropertyData_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -441,7 +441,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsPropertyType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -457,7 +457,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsPropertyTypeGroup_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -471,7 +471,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoRelation_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -485,7 +485,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoRelationType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -497,7 +497,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsStylesheet_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -510,7 +510,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsStylesheetProperty_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -523,7 +523,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsTags_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -535,7 +535,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsTagRelationship_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -555,7 +555,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsTask_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -571,7 +571,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_cmsTaskType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -608,7 +608,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoUser_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -621,7 +621,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoUserType_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -631,14 +631,11 @@ namespace Umbraco.Tests.Persistence } [Test] - public void Can_Create_umbracoUser2app_Table() + public void Can_Create_umbracoUserGroup_Table() { - using (Transaction transaction = Database.GetTransaction()) { - DatabaseSchemaHelper.CreateTable(); - DatabaseSchemaHelper.CreateTable(); - DatabaseSchemaHelper.CreateTable(); + DatabaseSchemaHelper.CreateTable(); //transaction.Complete(); } @@ -647,7 +644,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoUser2NodeNotify_Table() { - + using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -660,15 +657,40 @@ namespace Umbraco.Tests.Persistence } [Test] - public void Can_Create_umbracoUser2NodePermission_Table() + public void Can_Create_umbracoUser2UserGroup_Table() + { + using (Transaction transaction = Database.GetTransaction()) + { + DatabaseSchemaHelper.CreateTable(); + DatabaseSchemaHelper.CreateTable(); + DatabaseSchemaHelper.CreateTable(); + DatabaseSchemaHelper.CreateTable(); + + //transaction.Complete(); + } + } + + public void Can_Create_umbracoGroupUser2app_Table() { - using (Transaction transaction = Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); - DatabaseSchemaHelper.CreateTable(); - DatabaseSchemaHelper.CreateTable(); - DatabaseSchemaHelper.CreateTable(); + DatabaseSchemaHelper.CreateTable(); + DatabaseSchemaHelper.CreateTable(); + + //transaction.Complete(); + } + } + + [Test] + public void Can_Create_umbracoUserGroup2NodePermission_Table() + { + + using (Transaction transaction = Database.GetTransaction()) + { + DatabaseSchemaHelper.CreateTable(); + DatabaseSchemaHelper.CreateTable(); + DatabaseSchemaHelper.CreateTable(); //transaction.Complete(); } diff --git a/src/Umbraco.Tests/Services/SectionServiceTests.cs b/src/Umbraco.Tests/Services/SectionServiceTests.cs new file mode 100644 index 0000000000..e574d749f4 --- /dev/null +++ b/src/Umbraco.Tests/Services/SectionServiceTests.cs @@ -0,0 +1,102 @@ +using NUnit.Framework; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using System; +using System.Linq; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering the SectionService + /// + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture, RequiresSTA] + public class SectionServiceTests : BaseServiceTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + public override void CreateTestData() + { + base.CreateTestData(); + + ServiceContext.SectionService.MakeNew("Content", "content", "icon-content"); + ServiceContext.SectionService.MakeNew("Media", "media", "icon-media"); + ServiceContext.SectionService.MakeNew("Settings", "settings", "icon-settings"); + ServiceContext.SectionService.MakeNew("Developer", "developer", "icon-developer"); + } + + [Test] + public void SectionService_Can_Get_Allowed_Sections_For_User() + { + // Arrange + var user = CreateUser(); + + // Act + var result = ServiceContext.SectionService.GetAllowedSections(user.Id).ToList(); + + // Assert + Assert.AreEqual(2, result.Count); + } + + [Test] + public void SectionService_Can_Get_Allowed_Sections_For_User_With_Groups() + { + // Arrange + var user = CreateUser(true); + + // Act + var result = ServiceContext.SectionService.GetAllowedSections(user.Id).ToList(); + + // Assert + Assert.AreEqual(4, result.Count); + } + + private IUser CreateUser(bool withGroups = false) + { + var userType = new UserType + { + Alias = "TypeA", + Name = "Type A", + }; + ServiceContext.UserService.SaveUserType(userType, false); + + var user = new User(userType) + { + Name = "Test user", + Username = "testUser", + Email = "testuser@test.com", + }; + user.AddAllowedSection("content"); + user.AddAllowedSection("media"); + ServiceContext.UserService.Save(user, false); + + if (withGroups) + { + var userGroupA = new UserGroup + { + Alias = "GroupA", + Name = "Group A" + }; + userGroupA.AddAllowedSection("media"); + userGroupA.AddAllowedSection("settings"); + ServiceContext.UserService.SaveUserGroup(userGroupA, true, new[] { user.Id }, false); + + var userGroupB = new UserGroup + { + Alias = "GroupB", + Name = "Group B" + }; + userGroupB.AddAllowedSection("settings"); + userGroupB.AddAllowedSection("developer"); + ServiceContext.UserService.SaveUserGroup(userGroupB, true, new[] { user.Id }, false); + } + + return ServiceContext.UserService.GetUserById(user.Id); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 8659a73cd1..89053a2d5a 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -179,6 +179,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index d046ac7104..d68edb0ea4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -815,3 +815,8 @@ legend + .control-group { } } + +/* User/group selector */ +.group-selector .group-selector-list { float: left; } +.group-selector .group-selector-list div { height: 24px; } +.group-selector .group-selector-buttons { float: left; margin: 24px 16px; } diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 234b3a8f09..d679384a38 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -603,6 +603,7 @@ + diff --git a/src/Umbraco.Web.UI/config/trees.Release.config b/src/Umbraco.Web.UI/config/trees.Release.config index 9b2b8e8b6f..f53e3b7862 100644 --- a/src/Umbraco.Web.UI/config/trees.Release.config +++ b/src/Umbraco.Web.UI/config/trees.Release.config @@ -30,8 +30,9 @@ - - + + + diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 81c0c03bc8..30563647f8 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -26,7 +26,8 @@ - + + @@ -41,5 +42,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml index 9fca6d082a..7af0512ed7 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml @@ -1,5 +1,5 @@ - +
Template
/create/simple.ascx @@ -57,7 +57,7 @@ -
+
membergroup
/create/simple.ascx @@ -78,7 +78,7 @@ -
+
member
/create/member.ascx @@ -92,14 +92,14 @@ -
+
membergroup
/create/simple.ascx -
+
Stylesheet editor egenskab
/create/simple.ascx @@ -235,9 +235,17 @@
+ +
User Groups
+ /create/simple.ascx + + + + +
Macro
- /Create/PartialView.ascx + /Create/PartialView.ascx diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index f6859c6423..ba3df007e7 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -221,6 +221,14 @@
+ +
User Groups
+ /create/simple.ascx + + + + +
Macro
/Create/PartialView.ascx diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f710e54215..c4ffc9aefc 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -958,8 +958,8 @@ To manage your website, simply open the Umbraco back office and start adding con
Do not close this window during sorting]]>
- Validation - Validation errors must be fixed before the item can be saved + Validation + Validation errors must be fixed before the item can be saved Failed Insufficient user permissions, could not complete the operation Cancelled @@ -994,6 +994,7 @@ To manage your website, simply open the Umbraco back office and start adding con Error saving user (check log) User Saved User type saved + User group saved File not saved file could not be saved. Please check file permissions File saved @@ -1303,8 +1304,11 @@ To manage your website, simply open the Umbraco back office and start adding con Start Node in Content Name User permissions + User group permissions User type User types + User group + User groups Writer Translator Change 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 80c6faf07b..98075f00ac 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -993,6 +993,7 @@ To manage your website, simply open the Umbraco back office and start adding con Error saving user (check log) User Saved User type saved + User group saved File not saved file could not be saved. Please check file permissions File saved @@ -1308,8 +1309,11 @@ To manage your website, simply open the Umbraco back office and start adding con Start Node in Content Name User permissions + User group permissions User type User types + User group + User groups Writer Translator Change diff --git a/src/Umbraco.Web.UI/umbraco/users/EditUserGroup.aspx b/src/Umbraco.Web.UI/umbraco/users/EditUserGroup.aspx new file mode 100644 index 0000000000..6626f33eb1 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/users/EditUserGroup.aspx @@ -0,0 +1,52 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="EditUserGroup.aspx.cs" MasterPageFile="../masterpages/umbracoPage.Master" + Inherits="umbraco.cms.presentation.user.EditUserGroup" %> + +<%@ Register TagPrefix="cc2" Namespace="umbraco.uicontrols" Assembly="controls" %> + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Users not in group:
+ +
+
+ + +
+
+
Users in group:
+ +
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index f0c0861059..a6c24913ad 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -66,8 +66,8 @@ namespace Umbraco.Web.Security { get { - //only load it once per instance! - if (_currentUser == null) + //only load it once per instance! (but make sure groups are loaded) + if (_currentUser == null || _currentUser.GroupsLoaded == false) { var id = GetUserId(); if (id == -1) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7fb022e848..aec0cdab68 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -417,6 +417,17 @@ + + + EditUserGroup.aspx + ASPXCodeBehind + + + EditUserGroup.aspx + + + ASPXCodeBehind + @@ -2029,6 +2040,7 @@ ASPXCodeBehind
+ diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserGroups.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserGroups.cs new file mode 100644 index 0000000000..98e87ce34b --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserGroups.cs @@ -0,0 +1,50 @@ +using System; +using umbraco.BusinessLogic; +using System.Collections.Generic; +using umbraco.businesslogic; +using Umbraco.Core; + +namespace umbraco.cms.presentation.Trees +{ + [Tree(Constants.Applications.Users, "userGroups", "User Groups", sortOrder: 2)] + public class UserGroups : BaseTree + { + + public UserGroups(string application) : base(application) { } + + public override void RenderJS(ref System.Text.StringBuilder Javascript) + { + Javascript.Append( + @" +function openUserGroups(id) { + UmbClientMgr.contentFrame('users/EditUserGroup.aspx?id=' + id); +} +"); + } + + public override void Render(ref XmlTree tree) + { + List userGroups = UserGroup.GetAllUserGroups(); + foreach (UserGroup userGroup in userGroups) + { + XmlTreeNode node = XmlTreeNode.Create(this); + node.NodeID = userGroup.Id.ToString(); + node.Action = string.Format("javascript:openUserGroups({0})", userGroup.Id.ToString()); + node.Icon = "icon-users"; + node.Text = userGroup.Name; + + OnBeforeNodeRender(ref tree, ref node, EventArgs.Empty); + if (node != null) + { + tree.Add(node); + OnAfterNodeRender(ref tree, ref node, EventArgs.Empty); + } + } + } + + protected override void CreateRootNode(ref XmlTreeNode rootNode) + { + rootNode.Text = ui.Text("user", "userGroups"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserTypes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserTypes.cs index 8ac4771672..54dd21738b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserTypes.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserTypes.cs @@ -1,12 +1,4 @@ using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; using umbraco.BusinessLogic; using System.Collections.Generic; using umbraco.businesslogic; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index d9605506b4..b128903fdc 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -2,33 +2,25 @@ using System.Collections; using System.Configuration.Provider; using System.Globalization; -using System.IO; +using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; -using System.Xml; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Security; using Umbraco.Web; -using Umbraco.Web.Models; using Umbraco.Web.Security; using umbraco.BasePages; using umbraco.BusinessLogic; -using umbraco.businesslogic.Exceptions; -using umbraco.cms.businesslogic.media; using umbraco.cms.businesslogic.web; using umbraco.controls; using umbraco.presentation.channels.businesslogic; using umbraco.uicontrols; -using umbraco.providers; using umbraco.cms.presentation.Trees; using Umbraco.Core.IO; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Services; using PropertyType = umbraco.cms.businesslogic.propertytype.PropertyType; namespace umbraco.cms.presentation.user @@ -36,7 +28,7 @@ namespace umbraco.cms.presentation.user /// /// Summary description for EditUser. /// - public partial class EditUser : UmbracoEnsuredPage + public partial class EditUser : EditUserGroupsBase { public EditUser() { @@ -66,6 +58,13 @@ namespace umbraco.cms.presentation.user protected ContentPicker cContentPicker = new ContentPicker(); protected CustomValidator sectionValidator = new CustomValidator(); + protected UpdatePanel pnlGroups = new UpdatePanel(); + protected PlaceHolder pnlGroupControls = new PlaceHolder(); + protected ListBox lstInGroups = new ListBox(); + protected ListBox lstNotInGroups = new ListBox(); + protected Button btnAddGroup = new Button(); + protected Button btnRemoveGroup = new Button(); + protected Pane pp = new Pane(); private User u; @@ -154,7 +153,7 @@ namespace umbraco.cms.presentation.user var passwordChanger = (passwordChanger)LoadControl(SystemDirectories.Umbraco + "/controls/passwordChanger.ascx"); passwordChanger.MembershipProviderName = UmbracoSettings.DefaultBackofficeProvider; - //Add a custom validation message for the password changer + // Add a custom validation message for the password changer var passwordValidation = new CustomValidator { ID = "PasswordChangerValidator" @@ -180,22 +179,59 @@ namespace umbraco.cms.presentation.user pp.addProperty(ui.Text("user", "usertype", UmbracoUser), userType); pp.addProperty(ui.Text("user", "language", UmbracoUser), userLanguage); - //Media / content root nodes - Pane ppNodes = new Pane(); + // Media / content root nodes + var ppNodes = new Pane(); ppNodes.addProperty(ui.Text("user", "startnode", UmbracoUser), content); ppNodes.addProperty(ui.Text("user", "mediastartnode", UmbracoUser), medias); - //Generel umrbaco access - Pane ppAccess = new Pane(); + // General umbraco access + var ppAccess = new Pane(); ppAccess.addProperty(ui.Text("user", "noConsole", UmbracoUser), NoConsole); ppAccess.addProperty(ui.Text("user", "disabled", UmbracoUser), Disabled); - //access to which modules... - Pane ppModules = new Pane(); + // Access to which modules... + var ppModules = new Pane(); ppModules.addProperty(ui.Text("user", "modules", UmbracoUser), lapps); ppModules.addProperty(" ", sectionValidator); - TabPage userInfo = UserTabs.NewTabPage(u.Name); + // Groups + var ppGroups = new Pane(); + lstNotInGroups.SelectionMode = ListSelectionMode.Multiple; + btnAddGroup.Text = "Add"; + btnAddGroup.Click += btnAddToGroup_Click; + btnRemoveGroup.Text = "Remove"; + btnRemoveGroup.Click += btnRemoveFromGroup_Click; + lstInGroups.SelectionMode = ListSelectionMode.Multiple; + pnlGroups.ContentTemplateContainer.Controls.Add(pnlGroupControls); + pnlGroups.Attributes.Add("class", "group-selector"); + + var pnl1 = new Panel(); + pnl1.CssClass = "group-selector-list"; + var pnl1Header = new Panel(); + pnl1Header.Controls.Add(new Literal { Text = "Available groups" }); + pnl1.Controls.Add(pnl1Header); + pnl1.Controls.Add(lstNotInGroups); + + var pnl2 = new Panel(); + pnl2.CssClass = "group-selector-buttons"; + pnl2.Controls.Add(btnAddGroup); + pnl2.Controls.Add(btnRemoveGroup); + + var pnl3 = new Panel(); + pnl3.CssClass = "group-selector-list"; + var pnl3Header = new Panel(); + pnl3Header.Controls.Add(new Literal { Text = "Selected groups" }); + pnl3.Controls.Add(pnl3Header); + pnl3.Controls.Add(lstInGroups); + + pnlGroups.ContentTemplateContainer.Controls.Add(pnl1); + pnlGroups.ContentTemplateContainer.Controls.Add(pnl2); + pnlGroups.ContentTemplateContainer.Controls.Add(pnl3); + + ppModules.addProperty(ui.Text("user", "userGroups", UmbracoUser), pnlGroups); + BindGroups(); + + var userInfo = UserTabs.NewTabPage(u.Name); userInfo.Controls.Add(pp); @@ -203,6 +239,7 @@ namespace umbraco.cms.presentation.user userInfo.Controls.Add(ppNodes); userInfo.Controls.Add(ppModules); + userInfo.Controls.Add(ppGroups); userInfo.HasMenu = true; @@ -227,6 +264,33 @@ namespace umbraco.cms.presentation.user .SyncTree(UID.ToString(), IsPostBack); } + private void BindGroups() + { + var userService = ApplicationContext.Current.Services.UserService; + var allGroups = userService.GetAllUserGroups(); + var groupsForUser = userService.GetGroupsForUser(u.Id); + + lstInGroups.DataSource = groupsForUser; + lstInGroups.DataValueField = "Id"; + lstInGroups.DataTextField = "Name"; + lstInGroups.DataBind(); + + lstNotInGroups.DataSource = allGroups + .Where(x => groupsForUser.Select(y => y.Id).Contains(x.Id) == false); + lstNotInGroups.DataValueField = "Id"; + lstNotInGroups.DataTextField = "Name"; + lstNotInGroups.DataBind(); + } + + protected void btnAddToGroup_Click(object sender, EventArgs e) + { + MoveItems(lstNotInGroups, lstInGroups); + } + + protected void btnRemoveFromGroup_Click(object sender, EventArgs e) + { + MoveItems(lstInGroups, lstNotInGroups); + } void sectionValidator_ServerValidate(object source, ServerValidateEventArgs args) { @@ -276,7 +340,6 @@ namespace umbraco.cms.presentation.user } // Handle content and media pickers - PlaceHolder medias = new PlaceHolder(); cMediaPicker.AppAlias = Constants.Applications.Media; cMediaPicker.TreeAlias = "media"; @@ -336,7 +399,6 @@ namespace umbraco.cms.presentation.user ///
private void SetupForm() { - if (!IsPostBack) { MembershipUser user = BackOfficeProvider.GetUser(u.LoginName, false); @@ -487,6 +549,12 @@ namespace umbraco.cms.presentation.user if (li.Selected) u.AddApplication(li.Value); } + u.ClearGroups(); + foreach (ListItem li in lstInGroups.Items) + { + u.AddGroup(int.Parse(li.Value), li.Text); + } + u.Save(); // save data diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx new file mode 100644 index 0000000000..6626f33eb1 --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx @@ -0,0 +1,52 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="EditUserGroup.aspx.cs" MasterPageFile="../masterpages/umbracoPage.Master" + Inherits="umbraco.cms.presentation.user.EditUserGroup" %> + +<%@ Register TagPrefix="cc2" Namespace="umbraco.uicontrols" Assembly="controls" %> + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Users not in group:
+ +
+
+ + +
+
+
Users in group:
+ +
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx.cs new file mode 100644 index 0000000000..9b929bd931 --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Web.UI.WebControls; +using umbraco.BasePages; +using umbraco.BusinessLogic; +using umbraco.cms.presentation.Trees; +using umbraco.interfaces; +using Umbraco.Core; + +namespace umbraco.cms.presentation.user +{ + public partial class EditUserGroup : EditUserGroupsBase + { + public EditUserGroup() + { + CurrentApp = BusinessLogic.DefaultApps.users.ToString(); + } + + protected void Page_Load(object sender, EventArgs e) + { + pnlUmbraco.Text = umbraco.ui.Text("usergroup", base.getUser()); + + var save = pnlUmbraco.Menu.NewButton(); + save.Click += save_Click; + save.ID = "save"; + save.ToolTip = ui.Text("save"); + save.Text = ui.Text("save"); + + pp_alias.Text = umbraco.ui.Text("alias", base.getUser()); + pp_name.Text = umbraco.ui.Text("name", base.getUser()); + + pp_rights.Text = umbraco.ui.Text("default", base.getUser()) + " " + umbraco.ui.Text("rights", base.getUser()); + pp_sections.Text = umbraco.ui.Text("sections", base.getUser()); + + pp_users.Text = umbraco.ui.Text("users", base.getUser()); + + //ensure we have a query string + if (string.IsNullOrEmpty(Request.QueryString["id"])) + return; + //ensure it is an integer + if (!int.TryParse(Request.QueryString["id"], out m_userGroupID)) + return; + + if (!IsPostBack) + { + BindDetails(); + BindActions(); + BindSections(); + BindUsers(); + ClientTools + .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) + .SyncTree(m_userGroupID.ToString(), false); + } + } + + void save_Click(object sender, EventArgs e) + { + var userGroup = CurrentUserGroup; + userGroup.Name = txtUserGroupName.Text; + string actions = ""; + + foreach (ListItem li in cbl_rights.Items) + { + if (li.Selected) + actions += li.Value; + } + + userGroup.DefaultPermissions = actions; + + var userIds = new List(); + foreach (ListItem li in lstUsersInGroup.Items) + { + userIds.Add(int.Parse(li.Value)); + } + + userGroup.ClearApplications(); + foreach (ListItem li in cbl_sections.Items) + { + if (li.Selected) userGroup.AddApplication(li.Value); + } + + userGroup.SaveWithUsers(userIds.ToArray()); + + ClientTools.ShowSpeechBubble(speechBubbleIcon.save, ui.Text("speechBubbles", "editUserGroupSaved", base.getUser()), ""); + } + + protected List CurrentUserGroupActions + { + get + { + if (m_userGroupActions == null) + m_userGroupActions = umbraco.BusinessLogic.Actions.Action.FromString(CurrentUserGroup.DefaultPermissions); + return m_userGroupActions; + } + } + + protected UserGroup CurrentUserGroup + { + get + { + if (m_userGroup == null) + m_userGroup = UserGroup.GetUserGroup(m_userGroupID); + return m_userGroup; + } + } + + private UserGroup m_userGroup; + private List m_userGroupActions; + private int m_userGroupID; + + private void BindDetails() + { + lblUserGroupAlias.Text = CurrentUserGroup.Alias; + txtUserGroupName.Text = CurrentUserGroup.Name; + hidUserGroupID.Value = CurrentUserGroup.Id.ToString(); + } + + private void BindActions() + { + foreach (IAction ai in global::umbraco.BusinessLogic.Actions.Action.GetPermissionAssignable()) + { + + ListItem li = new ListItem(umbraco.ui.Text(ai.Alias, base.getUser()), ai.Letter.ToString()); + + if (CurrentUserGroupActions.Contains(ai)) + li.Selected = true; + + cbl_rights.Items.Add(li); + } + } + + private void BindSections() + { + string currentUserApps = ";"; + foreach (Application a in CurrentUser.Applications) + currentUserApps += a.alias + ";"; + + Application[] gapps = CurrentUserGroup.Applications; + foreach (Application app in BusinessLogic.Application.getAll()) + { + if (CurrentUser.IsAdmin() || currentUserApps.Contains(";" + app.alias + ";")) + { + ListItem li = new ListItem(ui.Text("sections", app.alias), app.alias); + foreach (Application tmp in gapps) if (app.alias == tmp.alias) li.Selected = true; + cbl_sections.Items.Add(li); + } + } + } + + private void BindUsers() + { + var userService = ApplicationContext.Current.Services.UserService; + + lstUsersInGroup.DataSource = userService.GetAllInGroup(CurrentUserGroup.Id); + lstUsersInGroup.DataValueField = "Id"; + lstUsersInGroup.DataTextField = "Name"; + lstUsersInGroup.DataBind(); + + lstUsersNotInGroup.DataSource = userService.GetAllNotInGroup(CurrentUserGroup.Id); + lstUsersNotInGroup.DataValueField = "Id"; + lstUsersNotInGroup.DataTextField = "Name"; + lstUsersNotInGroup.DataBind(); + } + + protected void btnAddToGroup_Click(object sender, EventArgs e) + { + MoveItems(lstUsersNotInGroup, lstUsersInGroup); + } + + protected void btnRemoveFromGroup_Click(object sender, EventArgs e) + { + MoveItems(lstUsersInGroup, lstUsersNotInGroup); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx.designer.cs new file mode 100644 index 0000000000..2de0f19ef0 --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroup.aspx.designer.cs @@ -0,0 +1,195 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace umbraco.cms.presentation.user { + + + public partial class EditUserGroup { + + /// + /// pnlUmbraco control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.UmbracoPanel pnlUmbraco; + + /// + /// pnl1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.Pane pnl1; + + /// + /// hidUserGroupID control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HiddenField hidUserGroupID; + + /// + /// pp_name control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.PropertyPanel pp_name; + + /// + /// txtUserGroupName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtUserGroupName; + + /// + /// pp_alias control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.PropertyPanel pp_alias; + + /// + /// lblUserGroupAlias control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblUserGroupAlias; + + /// + /// pnl2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.Pane pnl2; + + /// + /// pp_rights control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.PropertyPanel pp_rights; + + /// + /// cbl_rights control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CheckBoxList cbl_rights; + + /// + /// pnl3 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.Pane pnl3; + + /// + /// pp_sections control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.PropertyPanel pp_sections; + + /// + /// cbl_sections control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CheckBoxList cbl_sections; + + /// + /// pnl4 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.Pane pnl4; + + /// + /// pp_users control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::umbraco.uicontrols.PropertyPanel pp_users; + + /// + /// pnlUsers control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.UpdatePanel pnlUsers; + + /// + /// lstUsersNotInGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.ListBox lstUsersNotInGroup; + + /// + /// btnAddToGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnAddToGroup; + + /// + /// btnRemoveFromGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnRemoveFromGroup; + + /// + /// lstUsersInGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.ListBox lstUsersInGroup; + } +} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroupsBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroupsBase.cs new file mode 100644 index 0000000000..d5710423cf --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserGroupsBase.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.UI.WebControls; +using umbraco.BasePages; + +namespace umbraco.cms.presentation.user +{ + /// + /// Base class provided common functionality to the web forms that allow for assignment of users to groups + /// + public abstract class EditUserGroupsBase : UmbracoEnsuredPage + { + protected void MoveItems(ListBox from, ListBox to) + { + for (var i = from.Items.Count - 1; i >= 0; i--) + { + if (from.Items[i].Selected) + { + to.Items.Add(from.Items[i]); + var li = from.Items[i]; + from.Items.Remove(li); + } + } + + from.SelectedIndex = -1; + to.SelectedIndex = -1; + SortItems(to); + } + + protected void SortItems(ListBox listBox) + { + // Hat-tip: http://stackoverflow.com/a/3396527 + var list = new List(listBox.Items.Cast()); + list = list.OrderBy(li => li.Text).ToList(); + listBox.Items.Clear(); + listBox.Items.AddRange(list.ToArray()); + } + } +} \ No newline at end of file diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 59b6c19a41..3a3b5f14cd 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -1,19 +1,13 @@ using System; using System.Collections; -using System.Web.Caching; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Repositories; -using umbraco.DataLayer; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; +using umbraco.DataLayer; +using Umbraco.Core.Persistence.Querying; namespace umbraco.BusinessLogic { @@ -596,32 +590,14 @@ namespace umbraco.BusinessLogic /// /// Gets the users permissions based on a nodes path /// - /// The path. + /// The path. /// - public string GetPermissions(string Path) + public string GetPermissions(string path) { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - var defaultPermissions = UserType.DefaultPermissions; - - var cachedPermissions = ApplicationContext.Current.Services.UserService.GetPermissions(UserEntity) - .ToArray(); - - // NH 4.7.1 changing default permission behavior to default to User Type permissions IF no specific permissions has been - // set for the current node - var nodeId = Path.Contains(",") ? int.Parse(Path.Substring(Path.LastIndexOf(",", StringComparison.Ordinal) + 1)) : int.Parse(Path); - if (cachedPermissions.Any(x => x.EntityId == nodeId)) - { - var found = cachedPermissions.First(x => x.EntityId == nodeId); - return string.Join("", found.AssignedPermissions); - } - - // exception to everything. If default cruds is empty and we're on root node; allow browse of root node - if (string.IsNullOrEmpty(defaultPermissions) && Path == "-1") - defaultPermissions = "F"; - - // else return default user type cruds - return defaultPermissions; + var userService = ApplicationContext.Current.Services.UserService; + return userService.GetPermissionsForPath(UserEntity, path); } /// @@ -746,6 +722,32 @@ namespace umbraco.BusinessLogic ApplicationContext.Current.Services.UserService.Save(UserEntity); } + /// + /// Clears the list of groups the user is in, ensure to call Save afterwords + /// + public void ClearGroups() + { + if (_lazyId.HasValue) SetupUser(_lazyId.Value); + foreach (var group in UserEntity.Groups.ToArray()) + { + UserEntity.RemoveGroup(group); + } + } + + /// + /// Adds a group to the list groups for the user, ensure to call Save() afterwords + /// + public void AddGroup(int groupId, string groupName) + { + if (_lazyId.HasValue) SetupUser(_lazyId.Value); + UserEntity.AddGroup(new Umbraco.Core.Models.Membership.UserGroup + { + Id = groupId, + Name = groupName, + }); + } + + /// /// Gets or sets a value indicating whether the user has access to the Umbraco back end. /// diff --git a/src/umbraco.businesslogic/UserGroup.cs b/src/umbraco.businesslogic/UserGroup.cs new file mode 100644 index 0000000000..c04dad96e3 --- /dev/null +++ b/src/umbraco.businesslogic/UserGroup.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Events; + +namespace umbraco.BusinessLogic +{ + /// + /// Represents a umbraco Usergroup + /// + [Obsolete("Use the UserService instead")] + public class UserGroup + { + internal Umbraco.Core.Models.Membership.IUserGroup UserGroupItem; + + /// + /// Creates a new empty instance of a UserGroup + /// + public UserGroup() + { + UserGroupItem = new Umbraco.Core.Models.Membership.UserGroup(); + } + + internal UserGroup(Umbraco.Core.Models.Membership.IUserGroup userGroup) + { + UserGroupItem = userGroup; + } + + /// + /// Creates a new instance of a UserGroup and attempts to + /// load it's values from the database cache. + /// + /// + /// If the UserGroup is not found in the existing ID list, then this object + /// will remain an empty object + /// + /// The UserGroup id to find + public UserGroup(int id) + { + this.LoadByPrimaryKey(id); + } + + /// + /// Initializes a new instance of the class. + /// + /// The user type id. + /// The name. + public UserGroup(int id, string name) + { + UserGroupItem = new Umbraco.Core.Models.Membership.UserGroup(); + UserGroupItem.Id = id; + UserGroupItem.Name = name; + } + + /// + /// Creates a new instance of UserGroup with all parameters + /// + /// + /// + /// + /// + public UserGroup(int id, string name, string defaultPermissions, string alias) + { + UserGroupItem = new Umbraco.Core.Models.Membership.UserGroup(); + UserGroupItem.Id = id; + UserGroupItem.Name = name; + UserGroupItem.Alias = alias; + UserGroupItem.Permissions = defaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// The cache storage for all user groups + /// + private static List UserGroups + { + get + { + return ApplicationContext.Current.Services.UserService.GetAllUserGroups() + .Select(x => new UserGroup(x)) + .ToList(); + } + } + + #region Public Properties + /// + /// Gets or sets the user type alias. + /// + public string Alias + { + get { return UserGroupItem.Alias; } + set { UserGroupItem.Alias = value; } + } + + /// + /// Gets the name of the user type. + /// + public string Name + { + get { return UserGroupItem.Name; } + set { UserGroupItem.Name = value; } + } + + /// + /// Gets the id the user type + /// + public int Id + { + get { return UserGroupItem.Id; } + } + + /// + /// Gets the default permissions of the user type + /// + public string DefaultPermissions + { + get { return UserGroupItem.Permissions == null ? string.Empty : string.Join("", UserGroupItem.Permissions); } + set { UserGroupItem.Permissions = value.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); } + } + + /// + /// Gets the applications which the group has access to. + /// + /// The users applications. + public Application[] Applications + { + get + { + return GetApplications().ToArray(); + } + } + + /// + /// Get the application which the group has access to as a List + /// + /// + public List GetApplications() + { + var allApps = Application.getAll(); + var apps = new List(); + + var sections = UserGroupItem.AllowedSections; + + foreach (var s in sections) + { + var app = allApps.SingleOrDefault(x => x.alias == s); + if (app != null) + apps.Add(app); + } + + return apps; + } + + /// + /// Clears the list of applications the user has access to, ensure to call Save afterwords + /// + public void ClearApplications() + { + foreach (var s in UserGroupItem.AllowedSections.ToArray()) + { + UserGroupItem.RemoveAllowedSection(s); + } + } + + /// + /// Adds a application to the list of allowed applications, ensure to call Save() afterwords + /// + /// + public void AddApplication(string appAlias) + { + UserGroupItem.AddAllowedSection(appAlias); + } + + #endregion + + /// + /// Saves this instance + /// + public void Save() + { + PerformSave(); + } + + /// + /// Saves this instance (along with the list of users in the group if provided) + /// + public void SaveWithUsers(int[] userIds) + { + PerformSave(true, userIds); + } + + private void PerformSave(bool updateUsers = false, int[] userIds = null) + { + //ensure that this object has an ID specified (it exists in the database) + if (UserGroupItem.HasIdentity == false) + throw new Exception("The current UserGroup object does not exist in the database. New UserGroups should be created with the MakeNew method"); + + ApplicationContext.Current.Services.UserService.SaveUserGroup(UserGroupItem, updateUsers, userIds); + + //raise event + OnUpdated(this, new EventArgs()); + } + + /// + /// Deletes this instance. + /// + public void Delete() + { + //ensure that this object has an ID specified (it exists in the database) + if (UserGroupItem.HasIdentity == false) + throw new Exception("The current UserGroup object does not exist in the database. New UserGroups should be created with the MakeNew method"); + + ApplicationContext.Current.Services.UserService.DeleteUserGroup(UserGroupItem); + + //raise event + OnDeleted(this, new EventArgs()); + } + + /// + /// Load the data for the current UserGroup by it's id + /// + /// + /// Returns true if the UserGroup id was found + /// and the data was loaded, false if it wasn't + public bool LoadByPrimaryKey(int id) + { + UserGroupItem = ApplicationContext.Current.Services.UserService.GetUserGroupById(id); + return UserGroupItem != null; + } + + /// + /// Creates a new user group + /// + /// + /// + /// + public static UserGroup MakeNew(string name, string defaultPermissions, string alias) + { + //ensure that the current alias does not exist + //get the id for the new user type + var existing = UserGroups.Find(ut => (ut.Alias == alias)); + + if (existing != null) + throw new Exception("The UserGroup alias specified already exists"); + + var userType = new Umbraco.Core.Models.Membership.UserGroup + { + Alias = alias, + Name = name, + Permissions = defaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)) + }; + ApplicationContext.Current.Services.UserService.SaveUserGroup(userType); + + var legacy = new UserGroup(userType); + + //raise event + OnNew(legacy, new EventArgs()); + + return legacy; + } + + /// + /// Gets the user type with the specied ID + /// + /// The id. + /// + public static UserGroup GetUserGroup(int id) + { + return UserGroups.Find(ut => (ut.Id == id)); + } + + /// + /// Returns all user groups + /// + /// + public static List GetAllUserGroups() + { + return UserGroups; + } + + internal static event TypedEventHandler New; + private static void OnNew(UserGroup userType, EventArgs args) + { + if (New != null) + { + New(userType, args); + } + } + + internal static event TypedEventHandler Deleted; + private static void OnDeleted(UserGroup userType, EventArgs args) + { + if (Deleted != null) + { + Deleted(userType, args); + } + } + + internal static event TypedEventHandler Updated; + private static void OnUpdated(UserGroup userType, EventArgs args) + { + if (Updated != null) + { + Updated(userType, args); + } + } + } +} \ No newline at end of file diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj b/src/umbraco.businesslogic/umbraco.businesslogic.csproj index 1483523caf..0d6aae1b71 100644 --- a/src/umbraco.businesslogic/umbraco.businesslogic.csproj +++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj @@ -241,6 +241,7 @@ Code + Code From 2d9aeb3aa1595c715dcd5cc8ed8282259e63bda3 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Thu, 27 Oct 2016 18:11:46 +0200 Subject: [PATCH 007/510] Removed relationship between users and applications and got back to a compiling state. Set up editing screens for group/node permissions. --- src/Umbraco.Core/Cache/CacheKeys.cs | 2 + .../Models/Identity/BackOfficeIdentityUser.cs | 1 + .../Models/Identity/IdentityModelMappings.cs | 2 + .../Models/Membership/EntityPermission.cs | 6 +- .../Models/Membership/EntityPermissionSet.cs | 25 +- src/Umbraco.Core/Models/Membership/IUser.cs | 4 - src/Umbraco.Core/Models/Membership/User.cs | 92 +--- .../Membership/UserGroupEntityPermission.cs | 16 + src/Umbraco.Core/Models/Rdbms/User2AppDto.cs | 20 - .../Models/Rdbms/User2NodePermissionDto.cs | 23 - src/Umbraco.Core/Models/Rdbms/UserDto.cs | 3 - .../Persistence/Factories/UserFactory.cs | 23 - .../Factories/UserSectionFactory.cs | 35 -- .../Migrations/Initial/BaseDataCreation.cs | 16 - .../Relators/UserSectionRelator.cs | 50 --- .../Repositories/ContentRepository.cs | 8 +- .../DataTypeDefinitionRepository.cs | 2 +- .../Interfaces/IContentRepository.cs | 2 +- .../Interfaces/IUserGroupRepository.cs | 2 - .../Interfaces/IUserRepository.cs | 31 -- .../Repositories/PermissionRepository.cs | 239 +++++----- .../Repositories/UserGroupRepository.cs | 16 +- .../Repositories/UserRepository.cs | 136 +----- .../Security/BackOfficeUserStore.cs | 40 +- src/Umbraco.Core/Security/UserData.cs | 3 + src/Umbraco.Core/Services/ContentService.cs | 2 +- .../Services/ContentServiceExtensions.cs | 2 +- src/Umbraco.Core/Services/IContentService.cs | 2 +- src/Umbraco.Core/Services/ISectionService.cs | 2 +- src/Umbraco.Core/Services/IUserService.cs | 30 +- src/Umbraco.Core/Services/UserService.cs | 92 ++-- .../Services/UserServiceExtensions.cs | 13 +- src/Umbraco.Core/Umbraco.Core.csproj | 5 +- src/Umbraco.Tests/Models/UserTests.cs | 13 - .../Repositories/UserGroupRepositoryTest.cs | 425 ++++++++++++++++++ .../Repositories/UserRepositoryTest.cs | 139 +----- .../Services/SectionServiceTests.cs | 2 - .../Services/UserServiceTests.cs | 138 +++--- .../TestHelpers/Entities/MockedUser.cs | 17 +- .../TestHelpers/Entities/MockedUserGroup.cs | 17 + .../TreesAndSections/SectionTests.cs | 58 --- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 + .../ContentControllerUnitTests.cs | 12 +- ...terAllowedOutgoingContentAttributeTests.cs | 8 +- src/Umbraco.Web.UI/config/trees.config | 2 +- .../umbraco/users/PermissionEditor.aspx | 2 +- .../Cache/CacheRefresherEventHandler.cs | 34 +- src/Umbraco.Web/Cache/DistributedCache.cs | 5 +- .../Cache/DistributedCacheExtensions.cs | 14 +- .../Cache/UserGroupCacheRefresher.cs | 66 +++ .../UserGroupPermissionsCacheRefresher.cs | 58 +++ .../Cache/UserPermissionsCacheRefresher.cs | 58 --- src/Umbraco.Web/Security/WebSecurity.cs | 1 - src/Umbraco.Web/Umbraco.Web.csproj | 16 +- ...Permissions.cs => UserGroupPermissions.cs} | 165 ++++--- .../umbraco/dialogs/cruds.aspx.cs | 72 ++- .../umbraco/dialogs/moveOrCopy.aspx.cs | 7 +- .../umbraco/users/EditUser.aspx.cs | 54 +-- .../umbraco/users/NodePermissions.ascx.cs | 103 ++--- .../umbraco/users/PermissionEditor.aspx | 2 +- .../umbraco/users/PermissionEditor.aspx.cs | 79 ++-- .../umbraco/users/PermissionsHandler.asmx.cs | 85 ++-- ...Permissions.cs => UserGroupPermissions.cs} | 292 ++++++------ src/umbraco.businesslogic/User.cs | 54 --- src/umbraco.cms/businesslogic/CMSNode.cs | 5 +- src/umbraco.cms/businesslogic/Permission.cs | 105 +++-- 66 files changed, 1430 insertions(+), 1625 deletions(-) create mode 100644 src/Umbraco.Core/Models/Membership/UserGroupEntityPermission.cs delete mode 100644 src/Umbraco.Core/Models/Rdbms/User2AppDto.cs delete mode 100644 src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs delete mode 100644 src/Umbraco.Core/Persistence/Factories/UserSectionFactory.cs delete mode 100644 src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs create mode 100644 src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs create mode 100644 src/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs create mode 100644 src/Umbraco.Web/Cache/UserGroupCacheRefresher.cs create mode 100644 src/Umbraco.Web/Cache/UserGroupPermissionsCacheRefresher.cs delete mode 100644 src/Umbraco.Web/Cache/UserPermissionsCacheRefresher.cs rename src/Umbraco.Web/umbraco.presentation/umbraco/Trees/{UserPermissions.cs => UserGroupPermissions.cs} (57%) rename src/Umbraco.Web/umbraco.presentation/umbraco/users/{UserPermissions.cs => UserGroupPermissions.cs} (77%) diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index 0c1a202b66..596bf2455b 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -58,6 +58,8 @@ namespace Umbraco.Core.Cache public const string UserPermissionsCacheKey = "UmbracoUserPermissions"; + public const string UserGroupPermissionsCacheKey = "UmbracoUserGroupPermissions"; + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] public const string ContentTypeCacheKey = "UmbracoContentType"; diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 50dc7d06f8..1618150851 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -34,6 +34,7 @@ namespace Umbraco.Core.Models.Identity public int StartContentId { get; set; } public int StartMediaId { get; set; } public string[] AllowedSections { get; set; } + public string[] Groups { get; set; } public string Culture { get; set; } public string UserTypeAlias { get; set; } diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index 0dc95a8987..74fd602a7e 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -24,12 +24,14 @@ namespace Umbraco.Core.Models.Identity .ForMember(user => user.StartContentId, expression => expression.MapFrom(user => user.StartContentId)) .ForMember(user => user.UserTypeAlias, expression => expression.MapFrom(user => user.UserType.Alias)) .ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts)) + .ForMember(user => user.Groups, expression => expression.MapFrom(user => user.Groups.Select(x => x.Name).ToArray())) .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray())); config.CreateMap() .ConstructUsing((BackOfficeIdentityUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id' .ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id)) .ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections)) + .ForMember(detail => detail.Groups, opt => opt.MapFrom(user => user.Groups)) .ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name)) .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => new[] { user.UserTypeAlias })) .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) diff --git a/src/Umbraco.Core/Models/Membership/EntityPermission.cs b/src/Umbraco.Core/Models/Membership/EntityPermission.cs index 520a9b8d37..90430e26a8 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermission.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermission.cs @@ -3,18 +3,16 @@ namespace Umbraco.Core.Models.Membership { /// - /// Represents a user -> entity permission + /// Represents an entity permission (defined on the user group and derived to retrieve permissions for a given user) /// public class EntityPermission { - public EntityPermission(int userId, int entityId, string[] assignedPermissions) + public EntityPermission(int entityId, string[] assignedPermissions) { - UserId = userId; EntityId = entityId; AssignedPermissions = assignedPermissions; } - public int UserId { get; private set; } public int EntityId { get; private set; } /// diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs index c4669caf59..ea1e927a59 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs @@ -13,30 +13,31 @@ namespace Umbraco.Core.Models.Membership public int EntityId { get; private set; } /// - /// The key/value pairs of user id & single permission + /// The key/value pairs of user group id & single permission /// - public IEnumerable UserPermissionsSet { get; private set; } + public IEnumerable PermissionsSet { get; private set; } - public EntityPermissionSet(int entityId, IEnumerable userPermissionsSet) + public EntityPermissionSet(int entityId, IEnumerable permissionsSet) { EntityId = entityId; - UserPermissionsSet = userPermissionsSet; + PermissionsSet = permissionsSet; } - public class UserPermission + public class UserGroupPermission { - public UserPermission(int userId, string permission) + public UserGroupPermission(int groupId, string permission) { - UserId = userId; + UserGroupId = groupId; Permission = permission; } - public int UserId { get; private set; } + public int UserGroupId { get; private set; } + public string Permission { get; private set; } - protected bool Equals(UserPermission other) + protected bool Equals(UserGroupPermission other) { - return UserId == other.UserId && string.Equals(Permission, other.Permission); + return UserGroupId == other.UserGroupId && string.Equals(Permission, other.Permission); } public override bool Equals(object obj) @@ -44,14 +45,14 @@ namespace Umbraco.Core.Models.Membership if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((UserPermission) obj); + return Equals((UserGroupPermission) obj); } public override int GetHashCode() { unchecked { - return (UserId*397) ^ Permission.GetHashCode(); + return (UserGroupId * 397) ^ Permission.GetHashCode(); } } } diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index 095044c47e..a3cea0b232 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -47,10 +47,6 @@ namespace Umbraco.Core.Models.Membership IEnumerable AllowedSections { get; } - void RemoveAllowedSection(string sectionAlias); - - void AddAllowedSection(string sectionAlias); - /// /// Exposes the basic profile data /// diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 6fad1a6f78..41bcb5ee80 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -25,12 +25,8 @@ namespace Umbraco.Core.Models.Membership _defaultPermissions = _userType.Permissions == null ? Enumerable.Empty() : new List(_userType.Permissions); //Groups = new List { userType }; SessionTimeout = 60; - _sectionCollection = new ObservableCollection(); - _addedSections = new List(); - _removedSections = new List(); _groupCollection = new List(); _language = GlobalSettings.DefaultUILanguage; - _sectionCollection.CollectionChanged += SectionCollectionChanged; _isApproved = true; _isLockedOut = false; _startContentId = -1; @@ -55,9 +51,6 @@ namespace Umbraco.Core.Models.Membership private IUserType _userType; private string _name; private string _securityStamp; - private List _addedSections; - private List _removedSections; - private ObservableCollection _sectionCollection; private List _groupCollection; private bool _groupsLoaded; private int _sessionTimeout; @@ -92,7 +85,6 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); - public readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedSections); public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); @@ -200,22 +192,16 @@ namespace Umbraco.Core.Models.Membership public IEnumerable AllowedSections { - get { return _sectionCollection; } - } - - public void RemoveAllowedSection(string sectionAlias) - { - if (_sectionCollection.Contains(sectionAlias)) + get { - _sectionCollection.Remove(sectionAlias); - } - } + if (GroupsLoaded == false) + { + return Enumerable.Empty(); + } - public void AddAllowedSection(string sectionAlias) - { - if (_sectionCollection.Contains(sectionAlias) == false) - { - _sectionCollection.Add(sectionAlias); + return Groups + .SelectMany(x => x.AllowedSections) + .Distinct(); } } @@ -234,22 +220,6 @@ namespace Umbraco.Core.Models.Membership set { SetPropertyValueAndDetectChanges(value, ref _securityStamp, Ps.Value.SecurityStampSelector); } } - /// - /// Used internally to check if we need to add a section in the repository to the db - /// - internal IEnumerable AddedSections - { - get { return _addedSections; } - } - - /// - /// Used internally to check if we need to remove a section in the repository to the db - /// - internal IEnumerable RemovedSections - { - get { return _removedSections; } - } - /// /// Gets or sets the session timeout. /// @@ -363,61 +333,15 @@ namespace Umbraco.Core.Models.Membership #endregion - /// - /// Whenever resetting occurs, clear the remembered add/removed collections, even if - /// rememberPreviouslyChangedProperties is true, the AllowedSections property will still - /// be flagged as dirty. - /// - /// - public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) - { - _addedSections.Clear(); - _removedSections.Clear(); - base.ResetDirtyProperties(rememberPreviouslyChangedProperties); - } - - /// - /// Handles the collection changed event in order for us to flag the AllowedSections property as changed - /// - /// - /// - void SectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(Ps.Value.AllowedSectionsSelector); - - if (e.Action == NotifyCollectionChangedAction.Add) - { - var item = e.NewItems.Cast().First(); - - if (_addedSections.Contains(item) == false) - { - _addedSections.Add(item); - } - } - else if (e.Action == NotifyCollectionChangedAction.Remove) - { - var item = e.OldItems.Cast().First(); - - if (_removedSections.Contains(item) == false) - { - _removedSections.Add(item); - } - } - } - public override object DeepClone() { var clone = (User)base.DeepClone(); //turn off change tracking clone.DisableChangeTracking(); //need to create new collections otherwise they'll get copied by ref - clone._addedSections = new List(); - clone._removedSections = new List(); - clone._sectionCollection = new ObservableCollection(_sectionCollection.ToList()); clone._groupCollection = new List(_groupCollection.ToList()); clone._defaultPermissions = new List(_defaultPermissions.ToList()); //re-create the event handler - clone._sectionCollection.CollectionChanged += clone.SectionCollectionChanged; //this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); //re-enable tracking diff --git a/src/Umbraco.Core/Models/Membership/UserGroupEntityPermission.cs b/src/Umbraco.Core/Models/Membership/UserGroupEntityPermission.cs new file mode 100644 index 0000000000..619223f01a --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/UserGroupEntityPermission.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core.Models.Membership +{ + /// + /// Represents a user group -> entity permission + /// + public class UserGroupEntityPermission : EntityPermission + { + public UserGroupEntityPermission(int groupId, int entityId, string[] assignedPermissions) + : base(entityId, assignedPermissions) + { + UserGroupId = groupId; + } + + public int UserGroupId { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/User2AppDto.cs b/src/Umbraco.Core/Models/Rdbms/User2AppDto.cs deleted file mode 100644 index eafc6be230..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/User2AppDto.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoUser2app")] - [PrimaryKey("user", autoIncrement = false)] - [ExplicitColumns] - internal class User2AppDto - { - [Column("user")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_user2app", OnColumns = "user, app")] - [ForeignKey(typeof(UserDto))] - public int UserId { get; set; } - - [Column("app")] - [Length(50)] - public string AppAlias { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs b/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs deleted file mode 100644 index 1e6662735f..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoUser2NodePermission")] - [PrimaryKey("userId", autoIncrement = false)] - [ExplicitColumns] - internal class User2NodePermissionDto - { - [Column("userId")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_umbracoUser2NodePermission", OnColumns = "userId, nodeId, permission")] - [ForeignKey(typeof(UserDto))] - public int UserId { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - public int NodeId { get; set; } - - [Column("permission")] - public string Permission { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Models/Rdbms/UserDto.cs index 05a93c86bf..d05cf1086b 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserDto.cs @@ -73,8 +73,5 @@ namespace Umbraco.Core.Models.Rdbms [Column("lastLoginDate")] [NullSetting(NullSetting = NullSettings.Null)] public DateTime? LastLoginDate { get; set; } - - [ResultColumn] - public List User2AppDtos { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 322e8b5b65..a404c43450 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -43,14 +43,6 @@ namespace Umbraco.Core.Persistence.Factories user.LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue; user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue; - if (dto.User2AppDtos != null) - { - foreach (var app in dto.User2AppDtos) - { - user.AddAllowedSection(app.AppAlias); - } - } - //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 user.ResetDirtyProperties(false); @@ -77,7 +69,6 @@ namespace Umbraco.Core.Persistence.Factories UserLanguage = entity.Language, UserName = entity.Name, Type = short.Parse(entity.UserType.Id.ToString(CultureInfo.InvariantCulture)), - User2AppDtos = new List(), SecurityStampToken = entity.SecurityStamp, FailedLoginAttempts = entity.FailedPasswordAttempts, LastLockoutDate = entity.LastLockoutDate == DateTime.MinValue ? (DateTime?)null : entity.LastLockoutDate, @@ -85,20 +76,6 @@ namespace Umbraco.Core.Persistence.Factories LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, }; - foreach (var app in entity.AllowedSections) - { - var appDto = new User2AppDto - { - AppAlias = app - }; - if (entity.HasIdentity) - { - appDto.UserId = entity.Id; - } - - dto.User2AppDtos.Add(appDto); - } - if (entity.HasIdentity) { dto.Id = entity.Id.SafeCast(); diff --git a/src/Umbraco.Core/Persistence/Factories/UserSectionFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserSectionFactory.cs deleted file mode 100644 index 0e6a17594f..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/UserSectionFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class UserSectionFactory - { - private readonly IUser _user; - - public UserSectionFactory(IUser user) - { - _user = user; - } - - public IEnumerable BuildEntity(IEnumerable dto) - { - return dto.Select(x => x.AppAlias); - } - - public IEnumerable BuildDto(IEnumerable entity) - { - return entity.Select(x => new User2AppDto - { - //NOTE: We're force casting to int here! this might not work in the future - UserId = (int)_user.Id, - AppAlias = x - }); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 9570024b09..68d43a1acd 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -48,11 +48,6 @@ namespace Umbraco.Core.Persistence.Migrations.Initial CreateUmbracoUserTypeData(); } - if (tableName.Equals("umbracoUser2app")) - { - CreateUmbracoUser2AppData(); - } - if (tableName.Equals("cmsPropertyTypeGroup")) { CreateCmsPropertyTypeGroupData(); @@ -168,17 +163,6 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoUserType", "id", false, new UserTypeDto { Id = 4, Alias = "translator", Name = "Translator", DefaultPermissions = "AF" }); } - private void CreateUmbracoUser2AppData() - { - _database.Insert("umbracoUser2app", "user", false, new User2AppDto { UserId = 0, AppAlias = Constants.Applications.Content }); - _database.Insert("umbracoUser2app", "user", false, new User2AppDto { UserId = 0, AppAlias = Constants.Applications.Developer }); - _database.Insert("umbracoUser2app", "user", false, new User2AppDto { UserId = 0, AppAlias = Constants.Applications.Media }); - _database.Insert("umbracoUser2app", "user", false, new User2AppDto { UserId = 0, AppAlias = Constants.Applications.Members }); - _database.Insert("umbracoUser2app", "user", false, new User2AppDto { UserId = 0, AppAlias = Constants.Applications.Settings }); - _database.Insert("umbracoUser2app", "user", false, new User2AppDto { UserId = 0, AppAlias = Constants.Applications.Users }); - _database.Insert("umbracoUser2app", "user", false, new User2AppDto { UserId = 0, AppAlias = Constants.Applications.Forms }); - } - private void CreateCmsPropertyTypeGroupData() { _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); diff --git a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs deleted file mode 100644 index 923348e729..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class UserSectionRelator - { - internal UserDto Current; - - internal UserDto Map(UserDto a, User2AppDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same DictionaryItem as the current one we're processing - if (Current != null && Current.Id == a.Id) - { - if (p.AppAlias.IsNullOrWhiteSpace() == false) - { - // Yes, just add this User2AppDto to the current item's collection - Current.User2AppDtos.Add(p); - } - - // Return null to indicate we're not done with this User yet - return null; - } - - // This is a different User to the current one, or this is the - // first time through and we don't have one yet - - // Save the current User - var prev = Current; - - // Setup the new current User - Current = a; - Current.User2AppDtos = new List(); - //this can be null since we are doing a left join - if (p.AppAlias.IsNullOrWhiteSpace() == false) - { - Current.User2AppDtos.Add(p); - } - - // Return the now populated previous User (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index cafbabd286..03b3b9e6e9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -394,12 +394,12 @@ namespace Umbraco.Core.Persistence.Repositories // user's default permissions. if (parentPermissions.Any()) { - var userPermissions = ( + var userGroupPermissions = ( from perm in parentPermissions from p in perm.AssignedPermissions - select new EntityPermissionSet.UserPermission(perm.UserId, p)).ToList(); + select new EntityPermissionSet.UserGroupPermission(perm.UserGroupId, p)).ToList(); - permissionsRepo.ReplaceEntityPermissions(new EntityPermissionSet(entity.Id, userPermissions)); + permissionsRepo.ReplaceEntityPermissions(new EntityPermissionSet(entity.Id, userGroupPermissions)); //flag the entity's permissions changed flag so we can track those changes. //Currently only used for the cache refreshers to detect if we should refresh all user permissions cache. ((Content)entity).PermissionsChanged = true; @@ -791,7 +791,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; repo.AssignEntityPermission(entity, permission, userIds); } - public IEnumerable GetPermissionsForEntity(int entityId) + public IEnumerable GetPermissionsForEntity(int entityId) { var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); return repo.GetPermissionsForEntity(entityId); diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index a4d71885f8..b95d3f0d62 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -248,7 +248,7 @@ AND umbracoNode.id <> @id", Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); //Remove Permissions - Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); + Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); //Remove associated tags Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index e2555995d2..5f74a21d7a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -60,7 +60,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - IEnumerable GetPermissionsForEntity(int entityId); + IEnumerable GetPermissionsForEntity(int entityId); /// /// Used to add/update published xml for the content item diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs index e50d265228..45abdc70b4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs @@ -24,7 +24,6 @@ namespace Umbraco.Core.Persistence.Repositories /// Ids of users void AddUsersToGroup(int groupId, int[] userIds); - /* /// /// Gets the group permissions for the specified entities /// @@ -47,6 +46,5 @@ namespace Umbraco.Core.Persistence.Repositories /// Permissions as enumerable list of /// Specify the nodes to replace permissions for void AssignGroupPermission(int groupId, char permission, params int[] entityIds); - */ } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index 5645cafa2a..620350f563 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -22,12 +22,6 @@ namespace Umbraco.Core.Persistence.Repositories /// bool Exists(string username); - /// - /// This is useful when an entire section is removed from config - /// - /// - IEnumerable GetUsersAssignedToSection(string sectionAlias); - /// /// Gets all groups for a given user /// @@ -57,30 +51,5 @@ namespace Umbraco.Core.Persistence.Repositories /// /// IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy); - - - /// - /// Gets the user permissions for the specified entities - /// - /// - /// - /// - IEnumerable GetUserPermissionsForEntities(int userId, params int[] entityIds); - - /// - /// Replaces the same permission set for a single user to any number of entities - /// - /// - /// - /// - void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds); - - /// - /// Assigns the same permission set for a single user to any number of entities - /// - /// - /// - /// - void AssignUserPermission(int userId, char permission, params int[] entityIds); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index aa671dccec..83c31f6651 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -15,6 +15,15 @@ using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; using CacheKeys = Umbraco.Core.Cache.CacheKeys; using Umbraco.Core.Cache; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { @@ -38,196 +47,227 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// Returns permissions for a given user for any number of nodes + /// Returns permissions for a given group for any number of nodes /// - /// + /// /// /// - public IEnumerable GetUserPermissionsForEntities(int userId, params int[] entityIds) + public IEnumerable GetPermissionsForEntities(int groupId, params int[] entityIds) { - var entityIdKey = string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture))); + var entityIdKey = GetEntityIdKey(entityIds); return _runtimeCache.GetCacheItem>( - string.Format("{0}{1}{2}", CacheKeys.UserPermissionsCacheKey, userId, entityIdKey), + string.Format("{0}{1}{2}", + CacheKeys.UserGroupPermissionsCacheKey, + groupId, + entityIdKey), () => - { - - var whereBuilder = new StringBuilder(); - - //where userId = @userId AND - whereBuilder.Append(_sqlSyntax.GetQuotedColumnName("userId")); - whereBuilder.Append("="); - whereBuilder.Append(userId); - - if (entityIds.Any()) - { - whereBuilder.Append(" AND "); - - //where nodeId = @nodeId1 OR nodeId = @nodeId2, etc... - whereBuilder.Append("("); - for (var index = 0; index < entityIds.Length; index++) - { - var entityId = entityIds[index]; - whereBuilder.Append(_sqlSyntax.GetQuotedColumnName("nodeId")); - whereBuilder.Append("="); - whereBuilder.Append(entityId); - if (index < entityIds.Length - 1) - { - whereBuilder.Append(" OR "); - } - } - whereBuilder.Append(")"); - } - + { + var whereCriteria = GetPermissionsForEntitiesCriteria(groupId, entityIds); var sql = new Sql(); sql.Select("*") - .From() - .Where(whereBuilder.ToString()); - - //ToArray() to ensure it's all fetched from the db once. - var result = _unitOfWork.Database.Fetch(sql).ToArray(); + .From() + .Where(whereCriteria); + var result = _unitOfWork.Database.Fetch(sql).ToArray(); + // ToArray() to ensure it's all fetched from the db once return ConvertToPermissionList(result); - }, - //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will only have this exist in cache for 20 minutes, - // then it will refresh from the database. - new TimeSpan(0, 20, 0), - //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will make this priority below average - priority: CacheItemPriority.BelowNormal); + GetCacheTimeout(), + priority: GetCachePriority()); + } - } + private static string GetEntityIdKey(int[] entityIds) + { + return string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture))); + } + + private string GetPermissionsForEntitiesCriteria(int groupId, params int[] entityIds) + { + var whereBuilder = new StringBuilder(); + whereBuilder.Append(_sqlSyntax.GetQuotedColumnName("userGroupId")); + whereBuilder.Append("="); + whereBuilder.Append(groupId); + + if (entityIds.Any()) + { + whereBuilder.Append(" AND "); + + //where nodeId = @nodeId1 OR nodeId = @nodeId2, etc... + whereBuilder.Append("("); + for (var index = 0; index < entityIds.Length; index++) + { + var entityId = entityIds[index]; + whereBuilder.Append(_sqlSyntax.GetQuotedColumnName("nodeId")); + whereBuilder.Append("="); + whereBuilder.Append(entityId); + if (index < entityIds.Length - 1) + { + whereBuilder.Append(" OR "); + } + } + whereBuilder.Append(")"); + } + + return whereBuilder.ToString(); + } + + private static TimeSpan GetCacheTimeout() + { + //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will only have this exist in cache for 20 minutes, + // then it will refresh from the database. + return new TimeSpan(0, 20, 0); + } + + private static CacheItemPriority GetCachePriority() + { + //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will make this priority below average + return CacheItemPriority.BelowNormal; + } + + private static IEnumerable ConvertToPermissionList(IEnumerable result) + { + var permissions = new List(); + var nodePermissions = result.GroupBy(x => x.NodeId); + foreach (var np in nodePermissions) + { + var userGroupPermissions = np.GroupBy(x => x.UserGroupId); + foreach (var permission in userGroupPermissions) + { + var perms = permission.Select(x => x.Permission).ToArray(); + permissions.Add(new UserGroupEntityPermission(permission.Key, permission.First().NodeId, perms)); + } + } + + return permissions; + } /// - /// Returns permissions for all users for a given entity + /// Returns permissions for all groups for a given entity /// /// /// - public IEnumerable GetPermissionsForEntity(int entityId) + public IEnumerable GetPermissionsForEntity(int entityId) { var sql = new Sql(); sql.Select("*") - .From() - .Where(dto => dto.NodeId == entityId) - .OrderBy(dto => dto.NodeId); + .From() + .Where(dto => dto.NodeId == entityId) + .OrderBy(dto => dto.NodeId); - //ToArray() to ensure it's all fetched from the db once. - var result = _unitOfWork.Database.Fetch(sql).ToArray(); + var result = _unitOfWork.Database.Fetch(sql).ToArray(); + // ToArray() to ensure it's all fetched from the db once return ConvertToPermissionList(result); } /// - /// Assigns the same permission set for a single user to any number of entities + /// Assigns the same permission set for a single group to any number of entities /// - /// + /// /// /// /// /// This will first clear the permissions for this user and entities and recreate them /// - public void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds) + public void ReplacePermissions(int groupId, IEnumerable permissions, params int[] entityIds) { var db = _unitOfWork.Database; using (var trans = db.GetTransaction()) { //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)"; foreach (var idGroup in entityIds.InGroupsOf(2000)) { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND nodeId in (@nodeIds)", - new { userId = userId, nodeIds = idGroup }); + db.Execute(sql, new {groupId = groupId, nodeIds = idGroup}); } - var toInsert = new List(); + var toInsert = new List(); foreach (var p in permissions) { foreach (var e in entityIds) { - toInsert.Add(new User2NodePermissionDto + toInsert.Add(new UserGroup2NodePermissionDto { NodeId = e, Permission = p.ToString(CultureInfo.InvariantCulture), - UserId = userId + UserGroupId = groupId }); } } _unitOfWork.Database.BulkInsertRecords(toInsert, trans); - trans.Complete(); - //Raise the event AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(toInsert), false), this); + new SaveEventArgs(ConvertToPermissionList(toInsert), false), this); } } /// /// Assigns one permission for a user to many entities /// - /// + /// /// /// - public void AssignUserPermission(int userId, char permission, params int[] entityIds) + public void AssignPermission(int groupId, char permission, params int[] entityIds) { var db = _unitOfWork.Database; using (var trans = db.GetTransaction()) { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND permission=@permission AND nodeId in (@entityIds)", + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)"; + db.Execute(sql, new { - userId = userId, + groupId = groupId, permission = permission.ToString(CultureInfo.InvariantCulture), entityIds = entityIds }); - var actions = entityIds.Select(id => new User2NodePermissionDto + var actions = entityIds.Select(id => new UserGroup2NodePermissionDto { NodeId = id, Permission = permission.ToString(CultureInfo.InvariantCulture), - UserId = userId + UserGroupId = groupId }).ToArray(); _unitOfWork.Database.BulkInsertRecords(actions, trans); - trans.Complete(); - //Raise the event AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(actions), false), this); + new SaveEventArgs(ConvertToPermissionList(actions), false), this); } - } + } /// - /// Assigns one permission to an entity for multiple users + /// Assigns one permission to an entity for multiple groups /// /// /// - /// - public void AssignEntityPermission(TEntity entity, char permission, IEnumerable userIds) + /// + public void AssignEntityPermission(TEntity entity, char permission, IEnumerable groupIds) { var db = _unitOfWork.Database; using (var trans = db.GetTransaction()) { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId AND permission=@permission AND userId in (@userIds)", + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)"; + db.Execute(sql, new { - nodeId = entity.Id, + nodeId = entity.Id, permission = permission.ToString(CultureInfo.InvariantCulture), - userIds = userIds + groupIds = groupIds }); - var actions = userIds.Select(id => new User2NodePermissionDto + var actions = groupIds.Select(id => new UserGroup2NodePermissionDto { NodeId = entity.Id, Permission = permission.ToString(CultureInfo.InvariantCulture), - UserId = id + UserGroupId = id }).ToArray(); _unitOfWork.Database.BulkInsertRecords(actions, trans); - trans.Complete(); - //Raise the event AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(actions), false), this); + new SaveEventArgs(ConvertToPermissionList(actions), false), this); } } @@ -244,41 +284,24 @@ namespace Umbraco.Core.Persistence.Repositories var db = _unitOfWork.Database; using (var trans = db.GetTransaction()) { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId", new { nodeId = permissionSet.EntityId }); + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId"; + db.Execute(sql, new {nodeId = permissionSet.EntityId}); - var actions = permissionSet.UserPermissionsSet.Select(p => new User2NodePermissionDto + var actions = permissionSet.PermissionsSet.Select(p => new UserGroup2NodePermissionDto { NodeId = permissionSet.EntityId, Permission = p.Permission, - UserId = p.UserId + UserGroupId = p.UserGroupId }).ToArray(); _unitOfWork.Database.BulkInsertRecords(actions, trans); - trans.Complete(); - //Raise the event AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(actions), false), this); - } - } - - private static IEnumerable ConvertToPermissionList(IEnumerable result) - { - var permissions = new List(); - var nodePermissions = result.GroupBy(x => x.NodeId); - foreach (var np in nodePermissions) - { - var userPermissions = np.GroupBy(x => x.UserId); - foreach (var up in userPermissions) - { - var perms = up.Select(x => x.Permission).ToArray(); - permissions.Add(new EntityPermission(up.Key, up.First().NodeId, perms)); - } + new SaveEventArgs(ConvertToPermissionList(actions), false), this); } - return permissions; } - public static event TypedEventHandler, SaveEventArgs> AssignedPermissions; + public static event TypedEventHandler, SaveEventArgs> AssignedPermissions; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs index ba47270379..b13033c7b3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs @@ -76,16 +76,15 @@ namespace Umbraco.Core.Persistence.Repositories } } - /* /// /// Gets the group permissions for the specified entities /// /// Id of group /// Array of entity Ids - public IEnumerable GetGroupPermissionsForEntities(int groupId, params int[] entityIds) + public IEnumerable GetPermissionsForEntities(int groupId, params int[] entityIds) { - var repo = new GroupPermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - return repo.GetGroupPermissionsForEntities(groupId, entityIds); + var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + return repo.GetPermissionsForEntities(groupId, entityIds); } /// @@ -96,8 +95,8 @@ namespace Umbraco.Core.Persistence.Repositories /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed. public void ReplaceGroupPermissions(int groupId, IEnumerable permissions, params int[] entityIds) { - var repo = new GroupPermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.ReplaceGroupPermissions(groupId, permissions, entityIds); + var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + repo.ReplacePermissions(groupId, permissions, entityIds); } /// @@ -108,10 +107,9 @@ namespace Umbraco.Core.Persistence.Repositories /// Specify the nodes to replace permissions for public void AssignGroupPermission(int groupId, char permission, params int[] entityIds) { - var repo = new GroupPermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.AssignGroupPermission(groupId, permission, entityIds); + var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + repo.AssignPermission(groupId, permission, entityIds); } - */ #region Overrides of RepositoryBase diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 72d20ff660..f5e3fda7c5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -40,7 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.Fetch(new UserSectionRelator().Map, sql).FirstOrDefault(); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; @@ -74,7 +74,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoUser.id in (@ids)", new {ids = ids}); } - return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)) + return ConvertFromDtos(Database.Fetch(sql)) .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. } @@ -84,7 +84,7 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - var dtos = Database.Fetch(new UserSectionRelator().Map, sql) + var dtos = Database.Fetch(sql) .DistinctBy(x => x.Id); var users = ConvertFromDtos(dtos) @@ -121,9 +121,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql(); sql.Select(columns) - .From() - .LeftJoin() - .On(left => left.Id, right => right.UserId); + .From(); return sql; } @@ -139,9 +137,8 @@ namespace Umbraco.Core.Persistence.Repositories { "DELETE FROM cmsTask WHERE userId = @Id", "DELETE FROM cmsTask WHERE parentUserId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE userId = @Id", + "DELETE FROM umbracoUser2UserGroup WHERE userId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE userId = @Id", - "DELETE FROM umbracoUser2app WHERE " + SqlSyntax.GetQuotedColumnName("user") + "=@Id", "DELETE FROM umbracoUser WHERE id = @Id", "DELETE FROM umbracoExternalLogin WHERE id = @Id" }; @@ -168,13 +165,6 @@ namespace Umbraco.Core.Persistence.Repositories var id = Convert.ToInt32(Database.Insert(userDto)); entity.Id = id; - foreach (var sectionDto in userDto.User2AppDtos) - { - //need to set the id explicitly here - sectionDto.UserId = id; - Database.Insert(sectionDto); - } - entity.ResetDirtyProperties(); } @@ -238,52 +228,23 @@ namespace Umbraco.Core.Persistence.Repositories { Database.Update(userDto, changedCols); } - - //update the sections if they've changed + + //update the groups var user = (User)entity; - if (user.IsPropertyDirty("AllowedSections")) + + //first delete all + Database.Delete("WHERE UserId = @UserId", + new { UserId = user.Id }); + + //then re-add any associated with the user + foreach (var group in user.Groups) { - //now we need to delete any applications that have been removed - foreach (var section in user.RemovedSections) + var dto = new User2UserGroupDto { - //we need to manually delete this record because it has a composite key - Database.Delete("WHERE app = @Section AND " + SqlSyntax.GetQuotedColumnName("user") + "= @UserId", - new { Section = section, UserId = user.Id }); - } - - //for any that exist on the object, we need to determine if we need to update or insert - //NOTE: the User2AppDtos collection wil always be equal to the User.AllowedSections - foreach (var sectionDto in userDto.User2AppDtos) - { - //if something has been added then insert it - if (user.AddedSections.Contains(sectionDto.AppAlias)) - { - //we need to insert since this was added - Database.Insert(sectionDto); - } - else - { - //we need to manually update this record because it has a composite key - Database.Update("SET app=@Section WHERE app=@Section AND " + SqlSyntax.GetQuotedColumnName("user") + "=@UserId", - new { Section = sectionDto.AppAlias, UserId = sectionDto.UserId }); - } - } - - //update the groups - //first delete all - Database.Delete("WHERE UserId = @UserId", - new { UserId = user.Id }); - - //then re-add any associated with the user - foreach (var group in user.Groups) - { - var dto = new User2UserGroupDto - { - UserGroupId = group.Id, - UserId = user.Id - }; - Database.Insert(dto); - } + UserGroupId = group.Id, + UserId = user.Id + }; + Database.Insert(dto); } entity.ResetDirtyProperties(); @@ -316,29 +277,6 @@ namespace Umbraco.Core.Persistence.Repositories return Database.ExecuteScalar(sql) > 0; } - public IEnumerable GetUsersAssignedToSection(string sectionAlias) - { - //Here we're building up a query that looks like this, a sub query is required because the resulting structure - // needs to still contain all of the section rows per user. - - //SELECT * - //FROM [umbracoUser] - //LEFT JOIN [umbracoUser2app] - //ON [umbracoUser].[id] = [umbracoUser2app].[user] - //WHERE umbracoUser.id IN (SELECT umbracoUser.id - // FROM [umbracoUser] - // LEFT JOIN [umbracoUser2app] - // ON [umbracoUser].[id] = [umbracoUser2app].[user] - // WHERE umbracoUser2app.app = 'content') - - var sql = GetBaseQuery(false); - var innerSql = GetBaseQuery("umbracoUser.id"); - innerSql.Where("umbracoUser2app.app = " + SqlSyntax.GetQuotedValue(sectionAlias)); - sql.Where(string.Format("umbracoUser.id IN ({0})", innerSql.SQL)); - - return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)); - } - /// /// Gets all groups for a given user /// @@ -462,42 +400,6 @@ namespace Umbraco.Core.Persistence.Repositories return ids.Length == 0 ? Enumerable.Empty() : GetAll(ids).OrderBy(x => x.Id); } - /// - /// Returns permissions for a given user for any number of nodes - /// - /// - /// - /// - public IEnumerable GetUserPermissionsForEntities(int userId, params int[] entityIds) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - return repo.GetUserPermissionsForEntities(userId, entityIds); - } - - /// - /// Replaces the same permission set for a single user to any number of entities - /// - /// - /// - /// - public void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.ReplaceUserPermissions(userId, permissions, entityIds); - } - - /// - /// Assigns the same permission set for a single user to any number of entities - /// - /// - /// - /// - public void AssignUserPermission(int userId, char permission, params int[] entityIds) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.AssignUserPermission(userId, permission, entityIds); - } - #endregion private IEnumerable ConvertFromDtos(IEnumerable dtos) diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index c6714e256a..fcdf623dba 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -389,7 +389,7 @@ namespace Umbraco.Core.Security /// - /// Adds a user to a role (section) + /// Adds a user to a role (user group) /// /// /// @@ -406,18 +406,19 @@ namespace Umbraco.Core.Security throw new InvalidOperationException("The user id must be an integer to work with the Umbraco"); } - var found = _userService.GetUserById(asInt.Result); + var foundUser = _userService.GetUserById(asInt.Result); + var foundGroup = _userService.GetUserGroupByName(roleName); - if (found != null) + if (foundUser != null && foundGroup != null) { - found.AddAllowedSection(roleName); + foundUser.AddGroup(foundGroup); } return Task.FromResult(0); } /// - /// Removes the role (allowed section) for the user + /// Removes the role (user group) for the user /// /// /// @@ -434,18 +435,19 @@ namespace Umbraco.Core.Security throw new InvalidOperationException("The user id must be an integer to work with the Umbraco"); } - var found = _userService.GetUserById(asInt.Result); + var foundUser = _userService.GetUserById(asInt.Result); + var foundGroup = _userService.GetUserGroupByName(roleName); - if (found != null) + if (foundUser != null && foundGroup != null) { - found.RemoveAllowedSection(roleName); + foundUser.RemoveGroup(foundGroup); } return Task.FromResult(0); } /// - /// Returns the roles for this user + /// Returns the roles (user groups) for this user /// /// /// @@ -453,7 +455,7 @@ namespace Umbraco.Core.Security { ThrowIfDisposed(); if (user == null) throw new ArgumentNullException("user"); - return Task.FromResult((IList)user.AllowedSections.ToList()); + return Task.FromResult((IList)user.Groups.ToList()); } /// @@ -465,7 +467,7 @@ namespace Umbraco.Core.Security { ThrowIfDisposed(); if (user == null) throw new ArgumentNullException("user"); - return Task.FromResult(user.AllowedSections.InvariantContains(roleName)); + return Task.FromResult(user.Groups.InvariantContains(roleName)); } /// @@ -677,17 +679,21 @@ namespace Umbraco.Core.Security user.SecurityStamp = identityUser.SecurityStamp; } - if (user.AllowedSections.ContainsAll(identityUser.AllowedSections) == false - || identityUser.AllowedSections.ContainsAll(user.AllowedSections) == false) + if (user.Groups.Select(x => x.Name).ContainsAll(identityUser.Groups) == false + || identityUser.Groups.ContainsAll(user.Groups.Select(x => x.Name)) == false) { anythingChanged = true; - foreach (var allowedSection in user.AllowedSections) + foreach (var group in user.Groups) { - user.RemoveAllowedSection(allowedSection); + user.RemoveGroup(group); } - foreach (var allowedApplication in identityUser.AllowedSections) + foreach (var group in identityUser.Groups) { - user.AddAllowedSection(allowedApplication); + var foundGroup = _userService.GetUserGroupByName(group); + if (foundGroup != null) + { + user.AddGroup(foundGroup); + } } } diff --git a/src/Umbraco.Core/Security/UserData.cs b/src/Umbraco.Core/Security/UserData.cs index 407d2782dd..b1a8b08507 100644 --- a/src/Umbraco.Core/Security/UserData.cs +++ b/src/Umbraco.Core/Security/UserData.cs @@ -56,6 +56,9 @@ namespace Umbraco.Core.Security [DataMember(Name = "allowedApps")] public string[] AllowedApplications { get; set; } + [DataMember(Name = "groups")] + public string[] Groups { get; set; } + [DataMember(Name = "culture")] public string Culture { get; set; } } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 38b7aec9d7..8aa7030a8f 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -126,7 +126,7 @@ namespace Umbraco.Core.Services /// /// /// - public IEnumerable GetPermissionsForEntity(IContent content) + public IEnumerable GetPermissionsForEntity(IContent content) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateContentRepository(uow)) diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index 4919a81217..9c56cf28eb 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Services /// public static void RemoveContentPermissions(this IContentService contentService, int contentId) { - contentService.ReplaceContentPermissions(new EntityPermissionSet(contentId, Enumerable.Empty())); + contentService.ReplaceContentPermissions(new EntityPermissionSet(contentId, Enumerable.Empty())); } /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index ec5db8c4fb..6ee384c413 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -135,7 +135,7 @@ namespace Umbraco.Core.Services /// /// /// - IEnumerable GetPermissionsForEntity(IContent content); + IEnumerable GetPermissionsForEntity(IContent content); bool SendToPublication(IContent content, int userId = 0); diff --git a/src/Umbraco.Core/Services/ISectionService.cs b/src/Umbraco.Core/Services/ISectionService.cs index df5c89d4bf..d726179224 100644 --- a/src/Umbraco.Core/Services/ISectionService.cs +++ b/src/Umbraco.Core/Services/ISectionService.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Services IEnumerable
GetSections(); /// - /// Get the user's allowed sections + /// Get the user group's allowed sections /// /// /// diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 2a3ebf9828..fa69cb30a3 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -48,19 +48,19 @@ namespace Umbraco.Core.Services IUser GetUserById(int id); /// - /// Removes a specific section from all users + /// Removes a specific section from all user groups /// /// This is useful when an entire section is removed from config /// Alias of the section to remove - void DeleteSectionFromAllUsers(string sectionAlias); - + void DeleteSectionFromAllUserGroups(string sectionAlias); + /// - /// Add a specific section to all users or those specified as parameters + /// Add a specific section to all user groups or those specified as parameters /// - /// This is useful when a new section is created to allow specific users accessing it + /// This is useful when a new section is created to allow specific user groups to access it /// Alias of the section to add - /// Specifiying nothing will add the section to all user - void AddSectionToAllUsers(string sectionAlias, params int[] userIds); + /// Specifiying nothing will add the section to all user + void AddSectionToAllUserGroups(string sectionAlias, params int[] groupIds); /// /// Get permissions set for a user and optional node ids @@ -96,24 +96,24 @@ namespace Umbraco.Core.Services string GetPermissionsForPath(IUserGroup group, string path); /// - /// Replaces the same permission set for a single user to any number of entities + /// Replaces the same permission set for a single group to any number of entities /// - /// Id of the user + /// Id of the group /// /// Permissions as enumerable list of , - /// if no permissions are specified then all permissions for this node are removed for this user + /// if no permissions are specified then all permissions for this node are removed for this group /// /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed. - /// If no 'entityIds' are specified all permissions will be removed for the specified user. - void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds); + /// If no 'entityIds' are specified all permissions will be removed for the specified group. + void ReplaceUserGroupPermissions(int groupId, IEnumerable permissions, params int[] entityIds); /// - /// Assigns the same permission set for a single user to any number of entities + /// Assigns the same permission set for a single user group to any number of entities /// - /// Id of the user + /// Id of the group /// /// Specify the nodes to replace permissions for - void AssignUserPermission(int userId, char permission, params int[] entityIds); + void AssignUserGroupPermission(int groupId, char permission, params int[] entityIds); /// /// Gets a list of objects associated with a given group diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index fcd8c45536..1a5abb8ee7 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -147,9 +147,6 @@ namespace Umbraco.Core.Services IsLockedOut = false, IsApproved = true }; - //adding default sections content and media - user.AddAllowedSection("content"); - user.AddAllowedSection("media"); if (SavingUser.IsRaisedEventCancelled(new SaveEventArgs(user), this)) return user; @@ -584,33 +581,33 @@ namespace Umbraco.Core.Services } /// - /// Replaces the same permission set for a single user to any number of entities + /// Replaces the same permission set for a single group to any number of entities /// - /// If no 'entityIds' are specified all permissions will be removed for the specified user. - /// Id of the user + /// If no 'entityIds' are specified all permissions will be removed for the specified group. + /// Id of the group /// Permissions as enumerable list of /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed. - public void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds) + public void ReplaceUserGroupPermissions(int groupId, IEnumerable permissions, params int[] entityIds) { var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateUserRepository(uow)) + using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) { - repository.ReplaceUserPermissions(userId, permissions, entityIds); + repository.ReplaceGroupPermissions(groupId, permissions, entityIds); } } /// - /// Assigns the same permission set for a single user to any number of entities + /// Assigns the same permission set for a single user group to any number of entities /// - /// Id of the user + /// Id of the user group /// /// Specify the nodes to replace permissions for - public void AssignUserPermission(int userId, char permission, params int[] entityIds) + public void AssignUserGroupPermission(int groupId, char permission, params int[] entityIds) { var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateUserRepository(uow)) + using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) { - repository.AssignUserPermission(userId, permission, entityIds); + repository.AssignGroupPermission(groupId, permission, entityIds); } } @@ -843,48 +840,50 @@ namespace Umbraco.Core.Services /// /// This is useful when an entire section is removed from config /// Alias of the section to remove - public void DeleteSectionFromAllUsers(string sectionAlias) + public void DeleteSectionFromAllUserGroups(string sectionAlias) { var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateUserRepository(uow)) + using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) { - var assignedUsers = repository.GetUsersAssignedToSection(sectionAlias); - foreach (var user in assignedUsers) + var assignedGroups = repository.GetGroupsAssignedToSection(sectionAlias); + foreach (var group in assignedGroups) { //now remove the section for each user and commit - user.RemoveAllowedSection(sectionAlias); - repository.AddOrUpdate(user); + group.RemoveAllowedSection(sectionAlias); + repository.AddOrUpdate(group); } + uow.Commit(); } } - + /// - /// Add a specific section to all users or those specified as parameters + /// Add a specific section to all user groups or those specified as parameters /// - /// This is useful when a new section is created to allow specific users accessing it + /// This is useful when a new section is created to allow specific user groups to access it /// Alias of the section to add - /// Specifiying nothing will add the section to all user - public void AddSectionToAllUsers(string sectionAlias, params int[] userIds) + /// Specifiying nothing will add the section to all user + public void AddSectionToAllUserGroups(string sectionAlias, params int[] groupIds) { var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateUserRepository(uow)) + using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) { - IEnumerable users; - if (userIds.Any()) + IEnumerable groups; + if (groupIds.Any()) { - users = repository.GetAll(userIds); + groups = repository.GetAll(groupIds); } else { - users = repository.GetAll(); + groups = repository.GetAll(); } - foreach (var user in users.Where(u => !u.AllowedSections.InvariantContains(sectionAlias))) + foreach (var group in groups.Where(g => g.AllowedSections.InvariantContains(sectionAlias) == false)) { - //now add the section for each user and commit - user.AddAllowedSection(sectionAlias); - repository.AddOrUpdate(user); + //now add the section for each group and commit + group.AddAllowedSection(sectionAlias); + repository.AddOrUpdate(group); } + uow.Commit(); } } @@ -901,16 +900,15 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateUserRepository(uow)) { - var explicitPermissions = repository.GetUserPermissionsForEntities(user.Id, nodeIds); + // TODO: rework (to use groups - currently defaulting to user type) //if no permissions are assigned to a particular node then we will fill in those permissions with the user's defaults - var result = new List(explicitPermissions); + var result = new List(); var missingIds = nodeIds.Except(result.Select(x => x.EntityId)); foreach (var id in missingIds) { result.Add( new EntityPermission( - user.Id, id, user.DefaultPermissions.ToArray())); } @@ -939,16 +937,9 @@ namespace Umbraco.Core.Services /// An enumerable list of private IEnumerable GetPermissions(IUserRepository repository, IUser user, params int[] nodeIds) { - var explicitPermissions = repository.GetUserPermissionsForEntities(user.Id, nodeIds); - var result = new List(explicitPermissions); + var result = new List(); - // Save list of nodes that user has explicit permissions for - these take priority so we don't want - // to amend with any settings from groups (we'll take the special "null" action as not being defined - // as this is set if someone sets permissions on a user and then removes them) - var explicitlyDefinedEntityIds = result - .Where(IsNotNullActionPermission) - .Select(x => x.EntityId) - .ToArray(); + // TODO: rework to groups first, then user type // If no permissions are assigned to a particular node then, we will fill in those permissions with the user's defaults var missingIds = nodeIds.Except(result.Select(x => x.EntityId)); @@ -956,7 +947,6 @@ namespace Umbraco.Core.Services { result.Add( new EntityPermission( - user.Id, id, user.DefaultPermissions.ToArray())); } @@ -967,12 +957,8 @@ namespace Umbraco.Core.Services var groupPermissions = GetPermissions(group, nodeIds).ToList(); foreach (var groupPermission in groupPermissions) { - // Check if we already have this from explicitly set user permissions, if so, ignore it - if (explicitlyDefinedEntityIds.Contains(groupPermission.EntityId) == false) - { - // Add group permission, ensuring we keep a unique value for the entity Id in the list - AddOrAmendPermission(result, groupPermission); - } + // Add group permission, ensuring we keep a unique value for the entity Id in the list + AddOrAmendPermission(result, groupPermission); } } diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index afb679afa1..a1f480a1f5 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -10,21 +10,21 @@ namespace Umbraco.Core.Services /// Remove all permissions for this user for all nodes specified /// /// - /// + /// /// - public static void RemoveUserPermissions(this IUserService userService, int userId, params int[] entityIds) + public static void RemoveUserGroupPermissions(this IUserService userService, int groupId, params int[] entityIds) { - userService.ReplaceUserPermissions(userId, new char[] {}, entityIds); + userService.ReplaceUserGroupPermissions(groupId, new char[] {}, entityIds); } /// /// Remove all permissions for this user for all nodes /// /// - /// - public static void RemoveUserPermissions(this IUserService userService, int userId) + /// + public static void RemoveUserGroupPermissions(this IUserService userService, int groupId) { - userService.ReplaceUserPermissions(userId, new char[] { }); + userService.ReplaceUserGroupPermissions(groupId, new char[] { }); } /// @@ -62,7 +62,6 @@ namespace Umbraco.Core.Services member.ProviderUserKey == null ? member.UserName : member.ProviderUserKey.ToString(), Guid.NewGuid().ToString("N"), //pass cannot be empty writer); - user.AddAllowedSection(Constants.Applications.Content); userService.Save(user); return user; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 555c76eb4c..07256fda05 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -385,6 +385,7 @@ + @@ -787,7 +788,6 @@ - @@ -1034,7 +1034,6 @@ - @@ -1205,9 +1204,7 @@ - - diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index 189a4a17a8..9f974ee9fa 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -41,8 +41,6 @@ namespace Umbraco.Tests.Models Username = "username" }; - item.AddAllowedSection("test"); - var clone = (User)item.DeepClone(); Assert.AreNotSame(clone, item); @@ -61,17 +59,6 @@ namespace Umbraco.Tests.Models { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } - - //ensure internal collections are differet - Assert.AreNotSame(item.AddedSections, clone.AddedSections); - Assert.AreNotSame(item.RemovedSections, clone.RemovedSections); - - //ensure event handlers are still wired on clone - clone.AddAllowedSection("blah"); - Assert.AreEqual(1, clone.AddedSections.Count()); - clone.RemoveAllowedSection("blah"); - Assert.AreEqual(1, clone.RemovedSections.Count()); - } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs new file mode 100644 index 0000000000..a29aeb8c29 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs @@ -0,0 +1,425 @@ +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence; + +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Persistence.Repositories +{ + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class UserGroupRepositoryTest : BaseDatabaseFactoryTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + private UserGroupRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) + { + return new UserGroupRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); + } + + [Test] + public void Can_Perform_Add_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + + var userType = MockedUserGroup.CreateUserGroup(); + + // Act + repository.AddOrUpdate(userType); + unitOfWork.Commit(); + + // Assert + Assert.That(userType.HasIdentity, Is.True); + } + } + + [Test] + public void Can_Perform_Multiple_Adds_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + + var userType1 = MockedUserGroup.CreateUserGroup("1"); + var userType2 = MockedUserGroup.CreateUserGroup("2"); + + // Act + repository.AddOrUpdate(userType1); + unitOfWork.Commit(); + repository.AddOrUpdate(userType2); + unitOfWork.Commit(); + + // Assert + Assert.That(userType1.HasIdentity, Is.True); + Assert.That(userType2.HasIdentity, Is.True); + } + } + + [Test] + public void Can_Verify_Fresh_Entity_Is_Not_Dirty() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var userType = MockedUserGroup.CreateUserGroup(); + repository.AddOrUpdate(userType); + unitOfWork.Commit(); + + // Act + var resolved = repository.Get(userType.Id); + bool dirty = ((UserGroup) resolved).IsDirty(); + + // Assert + Assert.That(dirty, Is.False); + } + } + + [Test] + public void Can_Perform_Update_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var userType = MockedUserGroup.CreateUserGroup(); + repository.AddOrUpdate(userType); + unitOfWork.Commit(); + + // Act + var resolved = repository.Get(userType.Id); + resolved.Name = "New Name"; + resolved.Permissions = new[]{"Z", "Y", "X"}; + repository.AddOrUpdate(resolved); + unitOfWork.Commit(); + var updatedItem = repository.Get(userType.Id); + + // Assert + Assert.That(updatedItem.Id, Is.EqualTo(resolved.Id)); + Assert.That(updatedItem.Name, Is.EqualTo(resolved.Name)); + Assert.That(updatedItem.Permissions, Is.EqualTo(resolved.Permissions)); + } + } + + [Test] + public void Can_Perform_Delete_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + + var userType = MockedUserGroup.CreateUserGroup(); + + // Act + repository.AddOrUpdate(userType); + unitOfWork.Commit(); + var id = userType.Id; + + using (var repository2 = new UserGroupRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax)) + { + repository2.Delete(userType); + unitOfWork.Commit(); + + var resolved = repository2.Get(id); + + // Assert + Assert.That(resolved, Is.Null); + } + + } + } + + [Test] + public void Can_Perform_Get_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var userType = MockedUserGroup.CreateUserGroup(); + repository.AddOrUpdate(userType); + unitOfWork.Commit(); + + // Act + var resolved = repository.Get(userType.Id); + + // Assert + Assert.That(resolved.Id, Is.EqualTo(userType.Id)); + //Assert.That(resolved.CreateDate, Is.GreaterThan(DateTime.MinValue)); + //Assert.That(resolved.UpdateDate, Is.GreaterThan(DateTime.MinValue)); + Assert.That(resolved.Name, Is.EqualTo(userType.Name)); + Assert.That(resolved.Alias, Is.EqualTo(userType.Alias)); + Assert.That(resolved.Permissions, Is.EqualTo(userType.Permissions)); + } + } + + [Test] + public void Can_Perform_GetByQuery_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + CreateAndCommitMultipleUserGroups(repository, unitOfWork); + + // Act + var query = Query.Builder.Where(x => x.Alias == "testUserGroup1"); + var result = repository.GetByQuery(query); + + // Assert + Assert.That(result.Count(), Is.GreaterThanOrEqualTo(1)); + } + } + + [Test] + public void Can_Perform_GetAll_By_Param_Ids_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var userTypes = CreateAndCommitMultipleUserGroups(repository, unitOfWork); + + // Act + var result = repository.GetAll(userTypes[0].Id, userTypes[1].Id); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.Any(), Is.True); + Assert.That(result.Count(), Is.EqualTo(2)); + } + } + + [Test] + public void Can_Perform_GetAll_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + CreateAndCommitMultipleUserGroups(repository, unitOfWork); + + // Act + var result = repository.GetAll(); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.Any(), Is.True); + Assert.That(result.Count(), Is.GreaterThanOrEqualTo(3)); + } + } + + [Test] + public void Can_Perform_Exists_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var userTypes = CreateAndCommitMultipleUserGroups(repository, unitOfWork); + + // Act + var exists = repository.Exists(userTypes[0].Id); + + // Assert + Assert.That(exists, Is.True); + } + } + + [Test] + public void Can_Perform_Count_On_UserGroupRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var userTypes = CreateAndCommitMultipleUserGroups(repository, unitOfWork); + + // Act + var query = Query.Builder.Where(x => x.Alias == "testUserGroup1" || x.Alias == "testUserGroup2"); + var result = repository.Count(query); + + // Assert + Assert.That(result, Is.GreaterThanOrEqualTo(2)); + } + } + + [Test] + public void Can_Remove_Section_For_Group() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var groups = CreateAndCommitMultipleUserGroups(repository, unitOfWork); + + // Act + + //add and remove a few times, this tests the internal collection + groups[0].RemoveAllowedSection("content"); + groups[0].RemoveAllowedSection("content"); + groups[0].AddAllowedSection("content"); + groups[0].RemoveAllowedSection("content"); + + groups[1].RemoveAllowedSection("media"); + groups[1].RemoveAllowedSection("media"); + + repository.AddOrUpdate(groups[0]); + repository.AddOrUpdate(groups[1]); + unitOfWork.Commit(); + + // Assert + var result = repository.GetAll((int)groups[0].Id, (int)groups[1].Id).ToArray(); + Assert.AreEqual(1, result[0].AllowedSections.Count()); + Assert.AreEqual("media", result[0].AllowedSections.First()); + Assert.AreEqual(1, result[1].AllowedSections.Count()); + Assert.AreEqual("content", result[1].AllowedSections.First()); + } + } + + [Test] + public void Can_Add_Section_ForGroup() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var groups = CreateAndCommitMultipleUserGroups(repository, unitOfWork); + + // Act + + //add and remove a few times, this tests the internal collection + groups[0].AddAllowedSection("settings"); + groups[0].AddAllowedSection("settings"); + groups[0].RemoveAllowedSection("settings"); + groups[0].AddAllowedSection("settings"); + + groups[1].AddAllowedSection("developer"); + + //add the same even though it's already there + groups[2].AddAllowedSection("content"); + + repository.AddOrUpdate(groups[0]); + repository.AddOrUpdate(groups[1]); + unitOfWork.Commit(); + + // Assert + var result = repository.GetAll((int)groups[0].Id, (int)groups[1].Id, (int)groups[2].Id).ToArray(); + Assert.AreEqual(3, result[0].AllowedSections.Count()); + Assert.IsTrue(result[0].AllowedSections.Contains("content")); + Assert.IsTrue(result[0].AllowedSections.Contains("media")); + Assert.IsTrue(result[0].AllowedSections.Contains("settings")); + Assert.AreEqual(3, result[1].AllowedSections.Count()); + Assert.IsTrue(result[1].AllowedSections.Contains("content")); + Assert.IsTrue(result[1].AllowedSections.Contains("media")); + Assert.IsTrue(result[1].AllowedSections.Contains("developer")); + Assert.AreEqual(2, result[2].AllowedSections.Count()); + Assert.IsTrue(result[1].AllowedSections.Contains("content")); + Assert.IsTrue(result[1].AllowedSections.Contains("media")); + } + } + + [Test] + public void Can_Update_Section_For_Group() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var groups = CreateAndCommitMultipleUserGroups(repository, unitOfWork); + + // Act + + groups[0].RemoveAllowedSection("content"); + groups[0].AddAllowedSection("settings"); + + repository.AddOrUpdate(groups[0]); + unitOfWork.Commit(); + + // Assert + var result = repository.Get((int)groups[0].Id); + Assert.AreEqual(2, result.AllowedSections.Count()); + Assert.IsTrue(result.AllowedSections.Contains("settings")); + Assert.IsTrue(result.AllowedSections.Contains("media")); + } + } + + + [Test] + public void Get_Groups_Assigned_To_Section() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var user1 = MockedUserGroup.CreateUserGroup("1", new[] { "media" }); + var user2 = MockedUserGroup.CreateUserGroup("2", new[] { "settings" }); + var user3 = MockedUserGroup.CreateUserGroup("3", new[] { "settings" }); + repository.AddOrUpdate(user1); + repository.AddOrUpdate(user2); + repository.AddOrUpdate(user3); + unitOfWork.Commit(); + + // Act + + var groups = repository.GetGroupsAssignedToSection("test"); + + // Assert + Assert.AreEqual(2, groups.Count()); + var names = groups.Select(x => x.Name).ToArray(); + Assert.IsTrue(names.Contains("TestGroup1")); + Assert.IsTrue(names.Contains("TestGroup3")); + } + } + + private IUserGroup[] CreateAndCommitMultipleUserGroups(IUserGroupRepository repository, IUnitOfWork unitOfWork) + { + var userGroup1 = MockedUserGroup.CreateUserGroup("1"); + var userGroup2 = MockedUserGroup.CreateUserGroup("2"); + var userGroup3 = MockedUserGroup.CreateUserGroup("3"); + repository.AddOrUpdate(userGroup1); + repository.AddOrUpdate(userGroup2); + repository.AddOrUpdate(userGroup3); + unitOfWork.Commit(); + return new IUserGroup[] { userGroup1, userGroup2, userGroup3 }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 1d26e59113..2a92cb39c8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -135,7 +135,6 @@ namespace Umbraco.Tests.Persistence.Repositories resolved.StartMediaId = 11; resolved.Email = "new@new.com"; resolved.Username = "newName"; - resolved.RemoveAllowedSection("content"); repository.AddOrUpdate(resolved); unitOfWork.Commit(); @@ -341,142 +340,6 @@ namespace Umbraco.Tests.Persistence.Repositories } } - [Test] - public void Can_Remove_Section_For_User() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - UserTypeRepository userTypeRepository; - using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) - { - var users = CreateAndCommitMultipleUsers(repository, unitOfWork); - - // Act - - //add and remove a few times, this tests the internal collection - users[0].RemoveAllowedSection("content"); - users[0].RemoveAllowedSection("content"); - users[0].AddAllowedSection("content"); - users[0].RemoveAllowedSection("content"); - - users[1].RemoveAllowedSection("media"); - users[1].RemoveAllowedSection("media"); - - repository.AddOrUpdate(users[0]); - repository.AddOrUpdate(users[1]); - unitOfWork.Commit(); - - // Assert - var result = repository.GetAll((int) users[0].Id, (int) users[1].Id).ToArray(); - Assert.AreEqual(1, result[0].AllowedSections.Count()); - Assert.AreEqual("media", result[0].AllowedSections.First()); - Assert.AreEqual(1, result[1].AllowedSections.Count()); - Assert.AreEqual("content", result[1].AllowedSections.First()); - } - } - - [Test] - public void Can_Add_Section_For_User() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - UserTypeRepository userTypeRepository; - using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) - { - var users = CreateAndCommitMultipleUsers(repository, unitOfWork); - - // Act - - //add and remove a few times, this tests the internal collection - users[0].AddAllowedSection("settings"); - users[0].AddAllowedSection("settings"); - users[0].RemoveAllowedSection("settings"); - users[0].AddAllowedSection("settings"); - - users[1].AddAllowedSection("developer"); - - //add the same even though it's already there - users[2].AddAllowedSection("content"); - - repository.AddOrUpdate(users[0]); - repository.AddOrUpdate(users[1]); - unitOfWork.Commit(); - - // Assert - var result = repository.GetAll((int) users[0].Id, (int) users[1].Id, (int) users[2].Id).ToArray(); - Assert.AreEqual(3, result[0].AllowedSections.Count()); - Assert.IsTrue(result[0].AllowedSections.Contains("content")); - Assert.IsTrue(result[0].AllowedSections.Contains("media")); - Assert.IsTrue(result[0].AllowedSections.Contains("settings")); - Assert.AreEqual(3, result[1].AllowedSections.Count()); - Assert.IsTrue(result[1].AllowedSections.Contains("content")); - Assert.IsTrue(result[1].AllowedSections.Contains("media")); - Assert.IsTrue(result[1].AllowedSections.Contains("developer")); - Assert.AreEqual(2, result[2].AllowedSections.Count()); - Assert.IsTrue(result[1].AllowedSections.Contains("content")); - Assert.IsTrue(result[1].AllowedSections.Contains("media")); - } - } - - [Test] - public void Can_Update_Section_For_User() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - UserTypeRepository userTypeRepository; - using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) - { - var users = CreateAndCommitMultipleUsers(repository, unitOfWork); - - // Act - - users[0].RemoveAllowedSection("content"); - users[0].AddAllowedSection("settings"); - - repository.AddOrUpdate(users[0]); - unitOfWork.Commit(); - - // Assert - var result = repository.Get((int) users[0].Id); - Assert.AreEqual(2, result.AllowedSections.Count()); - Assert.IsTrue(result.AllowedSections.Contains("settings")); - Assert.IsTrue(result.AllowedSections.Contains("media")); - } - } - - - [Test] - public void Get_Users_Assigned_To_Section() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - UserTypeRepository userTypeRepository; - using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) - { - var user1 = MockedUser.CreateUser(CreateAndCommitUserType(), "1", "test", "media"); - var user2 = MockedUser.CreateUser(CreateAndCommitUserType(), "2", "media", "settings"); - var user3 = MockedUser.CreateUser(CreateAndCommitUserType(), "3", "test", "settings"); - repository.AddOrUpdate(user1); - repository.AddOrUpdate(user2); - repository.AddOrUpdate(user3); - unitOfWork.Commit(); - - // Act - - var users = repository.GetUsersAssignedToSection("test"); - - // Assert - Assert.AreEqual(2, users.Count()); - var names = users.Select(x => x.Username).ToArray(); - Assert.IsTrue(names.Contains("TestUser1")); - Assert.IsTrue(names.Contains("TestUser3")); - } - } - [Test] public void Default_User_Permissions_Based_On_User_Type() { @@ -488,7 +351,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var user1 = MockedUser.CreateUser(CreateAndCommitUserType(), "1", "test", "media"); + var user1 = MockedUser.CreateUser(CreateAndCommitUserType(), "1"); repository.AddOrUpdate(user1); unitOfWork.Commit(); diff --git a/src/Umbraco.Tests/Services/SectionServiceTests.cs b/src/Umbraco.Tests/Services/SectionServiceTests.cs index e574d749f4..3bfba08e76 100644 --- a/src/Umbraco.Tests/Services/SectionServiceTests.cs +++ b/src/Umbraco.Tests/Services/SectionServiceTests.cs @@ -71,8 +71,6 @@ namespace Umbraco.Tests.Services Username = "testUser", Email = "testuser@test.com", }; - user.AddAllowedSection("content"); - user.AddAllowedSection("media"); ServiceContext.UserService.Save(user, false); if (withGroups) diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index c7002ce79d..35013ca69b 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -185,7 +185,7 @@ namespace Umbraco.Tests.Services { var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); - var users = MockedUser.CreateUser(userType, 10); + var users = MockedUser.CreateMulipleUsers(userType, 10); ServiceContext.UserService.Save(users); //don't find this var customUser = MockedUser.CreateUser(userType); @@ -203,7 +203,7 @@ namespace Umbraco.Tests.Services { var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); - var users = MockedUser.CreateUser(userType, 10); + var users = MockedUser.CreateMulipleUsers(userType, 10); ServiceContext.UserService.Save(users); //include this var customUser = MockedUser.CreateUser(userType); @@ -221,7 +221,7 @@ namespace Umbraco.Tests.Services { var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); - var users = MockedUser.CreateUser(userType, 10); + var users = MockedUser.CreateMulipleUsers(userType, 10); ServiceContext.UserService.Save(users); //include this var customUser = MockedUser.CreateUser(userType); @@ -239,7 +239,7 @@ namespace Umbraco.Tests.Services { var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); - var users = MockedUser.CreateUser(userType, 10); + var users = MockedUser.CreateMulipleUsers(userType, 10); ServiceContext.UserService.Save(users); //include this var customUser = MockedUser.CreateUser(userType); @@ -257,7 +257,7 @@ namespace Umbraco.Tests.Services { var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); - var users = MockedUser.CreateUser(userType, 10); + var users = MockedUser.CreateMulipleUsers(userType, 10); ServiceContext.UserService.Save(users); int totalRecs; @@ -275,7 +275,7 @@ namespace Umbraco.Tests.Services { var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); - var users = MockedUser.CreateUser(userType, 10); + var users = MockedUser.CreateMulipleUsers(userType, 10); ServiceContext.UserService.Save(users); var customUser = MockedUser.CreateUser(userType); ServiceContext.UserService.Save(customUser); @@ -292,7 +292,7 @@ namespace Umbraco.Tests.Services { var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); - var users = MockedUser.CreateUser(userType, 10, (i, member) => member.LastLoginDate = DateTime.Now.AddMinutes(i * -2)); + var users = MockedUser.CreateMulipleUsers(userType, 10, (i, member) => member.LastLoginDate = DateTime.Now.AddMinutes(i * -2)); ServiceContext.UserService.Save(users); var customUser = MockedUser.CreateUser(userType); @@ -304,7 +304,7 @@ namespace Umbraco.Tests.Services { var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); - var users = MockedUser.CreateUser(userType, 10, (i, member) => member.IsLockedOut = i % 2 == 0); + var users = MockedUser.CreateMulipleUsers(userType, 10, (i, member) => member.IsLockedOut = i % 2 == 0); ServiceContext.UserService.Save(users); var customUser = MockedUser.CreateUser(userType); @@ -321,7 +321,7 @@ namespace Umbraco.Tests.Services { var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); - var users = MockedUser.CreateUser(userType, 10, (i, member) => member.IsApproved = i % 2 == 0); + var users = MockedUser.CreateMulipleUsers(userType, 10, (i, member) => member.IsApproved = i % 2 == 0); ServiceContext.UserService.Save(users); var customUser = MockedUser.CreateUser(userType); @@ -392,102 +392,124 @@ namespace Umbraco.Tests.Services } [Test] - public void Can_Add_And_Remove_Sections_From_User() + public void Can_Add_And_Remove_Sections_From_UserGroup() { - var userType = ServiceContext.UserService.GetUserTypeByAlias("admin"); + var userGroup = new UserGroup + { + Id = 1, + Alias = "Group1", + }; + userGroup.AddAllowedSection("content"); + userGroup.AddAllowedSection("mediat"); + ServiceContext.UserService.SaveUserGroup(userGroup); - var user1 = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com", userType); + var result1 = ServiceContext.UserService.GetUserGroupById(userGroup.Id); - var result1 = ServiceContext.UserService.GetUserById((int)user1.Id); - //expect 2 sections by default Assert.AreEqual(2, result1.AllowedSections.Count()); //adds some allowed sections - user1.AddAllowedSection("test1"); - user1.AddAllowedSection("test2"); - user1.AddAllowedSection("test3"); - user1.AddAllowedSection("test4"); - ServiceContext.UserService.Save(user1); + userGroup.AddAllowedSection("test1"); + userGroup.AddAllowedSection("test2"); + userGroup.AddAllowedSection("test3"); + userGroup.AddAllowedSection("test4"); + ServiceContext.UserService.SaveUserGroup(userGroup); + + result1 = ServiceContext.UserService.GetUserGroupById(userGroup.Id); - result1 = ServiceContext.UserService.GetUserById((int)user1.Id); - //expect 6 sections including the two default sections Assert.AreEqual(6, result1.AllowedSections.Count()); //simulate clearing the sections - foreach (var s in user1.AllowedSections) + foreach (var s in userGroup.AllowedSections) { result1.RemoveAllowedSection(s); } + //now just re-add a couple result1.AddAllowedSection("test3"); result1.AddAllowedSection("test4"); - ServiceContext.UserService.Save(result1); + ServiceContext.UserService.SaveUserGroup(result1); //assert //re-get - result1 = ServiceContext.UserService.GetUserById((int)user1.Id); + result1 = ServiceContext.UserService.GetUserGroupById(userGroup.Id); Assert.AreEqual(2, result1.AllowedSections.Count()); } [Test] - public void Can_Remove_Section_From_All_Assigned_Users() - { - var userType = ServiceContext.UserService.GetUserTypeByAlias("admin"); + public void Can_Remove_Section_From_All_Assigned_UserGroups() + { + var userGroup1 = new UserGroup + { + Id = 1, + Alias = "Group1", + }; + var userGroup2 = new UserGroup + { + Id = 2, + Alias = "Group2", + }; + ServiceContext.UserService.SaveUserGroup(userGroup1); + ServiceContext.UserService.SaveUserGroup(userGroup2); - var user1 = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com", userType); - var user2 = ServiceContext.UserService.CreateUserWithIdentity("test2", "test2@test.com", userType); - //adds some allowed sections - user1.AddAllowedSection("test"); - user2.AddAllowedSection("test"); - ServiceContext.UserService.Save(user1); - ServiceContext.UserService.Save(user2); + userGroup1.AddAllowedSection("test"); + userGroup2.AddAllowedSection("test"); + ServiceContext.UserService.SaveUserGroup(userGroup1); + ServiceContext.UserService.SaveUserGroup(userGroup2); //now clear the section from all users - ServiceContext.UserService.DeleteSectionFromAllUsers("test"); + ServiceContext.UserService.DeleteSectionFromAllUserGroups("test"); //assert - var result1 = ServiceContext.UserService.GetUserById((int)user1.Id); - var result2 = ServiceContext.UserService.GetUserById((int)user2.Id); + var result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); + var result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); Assert.IsFalse(result1.AllowedSections.Contains("test")); Assert.IsFalse(result2.AllowedSections.Contains("test")); } [Test] - public void Can_Add_Section_To_All_Users() + public void Can_Add_Section_To_All_UserGroups() { - var userType = ServiceContext.UserService.GetUserTypeByAlias("admin"); + var userGroup1 = new UserGroup + { + Id = 1, + Alias = "Group1", + }; + var userGroup2 = new UserGroup + { + Id = 2, + Alias = "Group2", + }; + var userGroup3 = new UserGroup + { + Id = 2, + Alias = "Group3", + }; + ServiceContext.UserService.SaveUserGroup(userGroup1); + ServiceContext.UserService.SaveUserGroup(userGroup2); + ServiceContext.UserService.SaveUserGroup(userGroup3); - var user1 = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com", userType); - var user2 = ServiceContext.UserService.CreateUserWithIdentity("test2", "test2@test.com", userType); - var user3 = ServiceContext.UserService.CreateUserWithIdentity("test3", "test3@test.com", userType); - var user4 = ServiceContext.UserService.CreateUserWithIdentity("test4", "test4@test.com", userType); - - //now add the section to specific users - ServiceContext.UserService.AddSectionToAllUsers("test", (int)user1.Id, (int)user2.Id); + //now add the section to specific groups + ServiceContext.UserService.AddSectionToAllUserGroups("test", userGroup1.Id, userGroup2.Id); //assert - var result1 = ServiceContext.UserService.GetUserById((int)user1.Id); - var result2 = ServiceContext.UserService.GetUserById((int)user2.Id); - var result3 = ServiceContext.UserService.GetUserById((int)user3.Id); - var result4 = ServiceContext.UserService.GetUserById((int)user4.Id); + var result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); + var result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); + var result3 = ServiceContext.UserService.GetUserGroupById(userGroup3.Id); Assert.IsTrue(result1.AllowedSections.Contains("test")); Assert.IsTrue(result2.AllowedSections.Contains("test")); Assert.IsFalse(result3.AllowedSections.Contains("test")); - Assert.IsFalse(result4.AllowedSections.Contains("test")); - //now add the section to all users - ServiceContext.UserService.AddSectionToAllUsers("test"); + //now add the section to all groups + ServiceContext.UserService.AddSectionToAllUserGroups("test"); //assert - result1 = ServiceContext.UserService.GetUserById((int)user1.Id); - result2 = ServiceContext.UserService.GetUserById((int)user2.Id); - result3 = ServiceContext.UserService.GetUserById((int)user3.Id); - result4 = ServiceContext.UserService.GetUserById((int)user4.Id); + result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); + result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); + result3 = ServiceContext.UserService.GetUserGroupById(userGroup3.Id); Assert.IsTrue(result1.AllowedSections.Contains("test")); Assert.IsTrue(result2.AllowedSections.Contains("test")); Assert.IsTrue(result3.AllowedSections.Contains("test")); - Assert.IsTrue(result4.AllowedSections.Contains("test")); } [Test] diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs index 15d1ab76fd..3ccdd70966 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs @@ -8,7 +8,7 @@ namespace Umbraco.Tests.TestHelpers.Entities { public class MockedUser { - internal static User CreateUser(IUserType userType = null, string suffix = "", params string[] allowedSections) + internal static User CreateUser(IUserType userType = null, string suffix = "") { if (userType == null) { @@ -29,23 +29,10 @@ namespace Umbraco.Tests.TestHelpers.Entities Username = "TestUser" + suffix }; - if (allowedSections.Any()) - { - foreach (var s in allowedSections) - { - user.AddAllowedSection(s); - } - } - else - { - user.AddAllowedSection("content"); - user.AddAllowedSection("media"); - } - return user; } - internal static IEnumerable CreateUser(IUserType userType, int amount, Action onCreating = null) + internal static IEnumerable CreateMulipleUsers(IUserType userType, int amount, Action onCreating = null) { var list = new List(); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs new file mode 100644 index 0000000000..f266f55332 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs @@ -0,0 +1,17 @@ +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Tests.TestHelpers.Entities +{ + public class MockedUserGroup + { + internal static UserGroup CreateUserGroup(string suffix = "", string[] permissions = null) + { + return new UserGroup() + { + Alias = "testUserGroup" + suffix, + Name = "TestUserGroup" + suffix, + Permissions = permissions ?? new[]{"A", "B", "C"} + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs b/src/Umbraco.Tests/TreesAndSections/SectionTests.cs index 67ef50af38..1edd10aec7 100644 --- a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs +++ b/src/Umbraco.Tests/TreesAndSections/SectionTests.cs @@ -70,64 +70,6 @@ namespace Umbraco.Tests.TreesAndSections Assert.IsNull(ApplicationContext.Current.Services.SectionService.GetByAlias(name)); } - /// - /// Creates a new user, assigns the user to existing application, - /// then deletes the user - /// - [Test()] - public void Application_Create_New_User_Assign_Application_And_Delete_User() - { - var name = Guid.NewGuid().ToString("N"); - - //new user - var ut = UserType.GetAllUserTypes().First(); - var user = User.MakeNew(name, name, name, ut); - - //get application - //var app = Application.getAll().First(); - - //assign the app - user.addApplication(Constants.Applications.Content); - //ensure it's added - Assert.AreEqual(1, user.Applications.Count(x => x.alias == Constants.Applications.Content)); - - //delete the user - user.delete(); - - //make sure the assigned applications are gone - Assert.AreEqual(0, user.Applications.Count(x => x.alias == name)); - } - - /// - /// create a new application and assigne an new user and deletes the application making sure the assignments are removed - /// - [Test()] - public void Application_Make_New_Assign_User_And_Delete() - { - var name = Guid.NewGuid().ToString("N"); - - //new user - var ut = UserType.GetAllUserTypes().First(); - var user = User.MakeNew(name, name, name, ut); - - ApplicationContext.Current.Services.SectionService.MakeNew(name, name, "icon.jpg"); - - //check if it exists - var app = ApplicationContext.Current.Services.SectionService.GetByAlias(name); - Assert.IsNotNull(app); - - //assign the app - user.addApplication(app.Alias); - //ensure it's added - Assert.AreEqual(1, user.Applications.Count(x => x.alias == app.Alias)); - - //delete the app - ApplicationContext.Current.Services.SectionService.DeleteSection(app); - - //make sure the assigned applications are gone - Assert.AreEqual(0, user.Applications.Count(x => x.alias == name)); - } - #region Tests to write diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 89053a2d5a..c0f43220d9 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -179,8 +179,10 @@ + + diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs index 4b78df671d..9634bb164a 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs @@ -100,7 +100,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(9, 1234, new string[]{ "A", "B", "C" }) + new EntityPermission(1234, new string[]{ "A", "B", "C" }) }; userServiceMock.Setup(x => x.GetPermissions(user, 1234)).Returns(permissions); var userService = userServiceMock.Object; @@ -129,7 +129,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(9, 1234, new string[]{ "A", "F", "C" }) + new EntityPermission(1234, new string[]{ "A", "F", "C" }) }; userServiceMock.Setup(x => x.GetPermissions(user, 1234)).Returns(permissions); var userService = userServiceMock.Object; @@ -217,7 +217,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(9, 1234, new string[]{ "A" }) + new EntityPermission(1234, new string[]{ "A" }) }; userServiceMock.Setup(x => x.GetPermissions(user, -1)).Returns(permissions); var userService = userServiceMock.Object; @@ -241,7 +241,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(9, 1234, new string[]{ "A" }) + new EntityPermission(1234, new string[]{ "A" }) }; userServiceMock.Setup(x => x.GetPermissions(user, -1)).Returns(permissions); var userService = userServiceMock.Object; @@ -265,7 +265,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(9, 1234, new string[]{ "A" }) + new EntityPermission(1234, new string[]{ "A" }) }; userServiceMock.Setup(x => x.GetPermissions(user, -20)).Returns(permissions); var userService = userServiceMock.Object; @@ -289,7 +289,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(9, 1234, new string[]{ "A" }) + new EntityPermission(1234, new string[]{ "A" }) }; userServiceMock.Setup(x => x.GetPermissions(user, -20)).Returns(permissions); var userService = userServiceMock.Object; diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs index 1ebe2fc748..c776924af7 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs @@ -112,10 +112,10 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //we're only assigning 3 nodes browse permissions so that is what we expect as a result var permissions = new List { - new EntityPermission(9, 1, new string[]{ "F" }), - new EntityPermission(9, 2, new string[]{ "F" }), - new EntityPermission(9, 3, new string[]{ "F" }), - new EntityPermission(9, 4, new string[]{ "A" }) + new EntityPermission(1, new string[]{ "F" }), + new EntityPermission(2, new string[]{ "F" }), + new EntityPermission(3, new string[]{ "F" }), + new EntityPermission(4, new string[]{ "A" }) }; userServiceMock.Setup(x => x.GetPermissions(user, ids)).Returns(permissions); var userService = userServiceMock.Object; diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 30563647f8..cc63766afc 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -27,7 +27,7 @@ - + diff --git a/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx b/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx index 815a420f88..fc9270e370 100644 --- a/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx +++ b/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx @@ -27,7 +27,7 @@ - diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 638d124658..b5743df158 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -36,9 +36,9 @@ namespace Umbraco.Web.Cache SectionService.Deleted += ApplicationDeleted; SectionService.New += ApplicationNew; - //bind to user / user type events - UserService.SavedUserType += UserServiceSavedUserType; - UserService.DeletedUserType += UserServiceDeletedUserType; + //bind to user / user group events + UserService.SavedUserGroup += UserServiceSavedUserGroup; + UserService.DeletedUserGroup += UserServiceDeletedUserGroup; UserService.SavedUser += UserServiceSavedUser; UserService.DeletedUser += UserServiceDeletedUser; @@ -349,19 +349,6 @@ namespace Umbraco.Web.Cache } #endregion - #region UserType event handlers - static void UserServiceDeletedUserType(IUserService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserTypeCache(x.Id)); - } - - static void UserServiceSavedUserType(IUserService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserTypeCache(x.Id)); - } - - #endregion - #region Dictionary event handlers static void LocalizationServiceSavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) @@ -537,7 +524,17 @@ namespace Umbraco.Web.Cache { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id)); } - + + static void UserServiceSavedUserGroup(IUserService sender, SaveEventArgs e) + { + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserGroupCache(x.Id)); + } + + static void UserServiceDeletedUserGroup(IUserService sender, DeleteEventArgs e) + { + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserGroupCache(x.Id)); + } + private static void InvalidateCacheForPermissionsChange(UserGroupPermission sender) { if (sender.UserGroup != null) diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index 75715565ac..6956df2374 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -35,7 +35,6 @@ namespace Umbraco.Web.Cache public const string UserCacheRefresherId = "E057AF6D-2EE6-41F4-8045-3694010F0AA6"; public const string UserGroupCacheRefresherId = "45178038-B232-4FE8-AA1A-F2B949C44762"; public const string UserGroupPermissionsCacheRefresherId = "840AB9C5-5C0B-48DB-A77E-29FE4B80CD3A"; - public const string UserTypeCacheRefresherId = "7E707E21-0195-4522-9A3C-658CC1761BD4"; public const string ContentTypeCacheRefresherId = "6902E22C-9C10-483C-91F3-66B7CAE9E2F5"; public const string LanguageCacheRefresherId = "3E0F95D8-0BE5-44B8-8394-2B8750B62654"; public const string DomainCacheRefresherId = "11290A79-4B57-4C99-AD72-7748A3CF38AF"; @@ -58,8 +57,8 @@ namespace Umbraco.Web.Cache public static readonly Guid MediaCacheRefresherGuid = new Guid(MediaCacheRefresherId); public static readonly Guid MacroCacheRefresherGuid = new Guid(MacroCacheRefresherId); public static readonly Guid UserCacheRefresherGuid = new Guid(UserCacheRefresherId); + public static readonly Guid UserGroupCacheRefresherGuid = new Guid(UserGroupCacheRefresherId); public static readonly Guid UserGroupPermissionsCacheRefresherGuid = new Guid(UserGroupPermissionsCacheRefresherId); - public static readonly Guid UserTypeCacheRefresherGuid = new Guid(UserTypeCacheRefresherId); public static readonly Guid ContentTypeCacheRefresherGuid = new Guid(ContentTypeCacheRefresherId); public static readonly Guid LanguageCacheRefresherGuid = new Guid(LanguageCacheRefresherId); public static readonly Guid DomainCacheRefresherGuid = new Guid(DomainCacheRefresherId); diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index f7abb7494d..1050517c5a 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -41,25 +41,6 @@ namespace Umbraco.Web.Cache #endregion - #region User type cache - - public static void RemoveUserTypeCache(this DistributedCache dc, int userTypeId) - { - dc.Remove(DistributedCache.UserTypeCacheRefresherGuid, userTypeId); - } - - public static void RefreshUserTypeCache(this DistributedCache dc, int userTypeId) - { - dc.Refresh(DistributedCache.UserTypeCacheRefresherGuid, userTypeId); - } - - public static void RefreshAllUserTypeCache(this DistributedCache dc) - { - dc.RefreshAll(DistributedCache.UserTypeCacheRefresherGuid); - } - - #endregion - #region User cache public static void RemoveUserCache(this DistributedCache dc, int userId) @@ -75,7 +56,26 @@ namespace Umbraco.Web.Cache public static void RefreshAllUserCache(this DistributedCache dc) { dc.RefreshAll(DistributedCache.UserCacheRefresherGuid); - } + } + + #endregion + + #region User group cache + + public static void RemoveUserGroupCache(this DistributedCache dc, int userId) + { + dc.Remove(DistributedCache.UserGroupCacheRefresherGuid, userId); + } + + public static void RefreshUserGroupCache(this DistributedCache dc, int userId) + { + dc.Refresh(DistributedCache.UserGroupCacheRefresherGuid, userId); + } + + public static void RefreshAllUserGroupCache(this DistributedCache dc) + { + dc.RefreshAll(DistributedCache.UserGroupCacheRefresherGuid); + } #endregion diff --git a/src/Umbraco.Web/Cache/UserTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/UserTypeCacheRefresher.cs deleted file mode 100644 index ca3f93c068..0000000000 --- a/src/Umbraco.Web/Cache/UserTypeCacheRefresher.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Models.Membership; - -using Umbraco.Core.Persistence.Repositories; - -namespace Umbraco.Web.Cache -{ - /// - /// Handles User type cache invalidation/refreshing - /// - public sealed class UserTypeCacheRefresher : CacheRefresherBase - { - protected override UserTypeCacheRefresher Instance - { - get { return this; } - } - - public override Guid UniqueIdentifier - { - get { return Guid.Parse(DistributedCache.UserTypeCacheRefresherId); } - } - - public override string Name - { - get { return "User type cache refresher"; } - } - - public override void RefreshAll() - { - ClearAllIsolatedCacheByEntityType(); - base.RefreshAll(); - } - - public override void Refresh(int id) - { - var userTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); - if (userTypeCache) - userTypeCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); - base.Refresh(id); - } - - public override void Remove(int id) - { - var userTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); - if (userTypeCache) - userTypeCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); - base.Remove(id); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 199ffd9bed..bc596a8a0a 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -604,60 +604,49 @@ namespace Umbraco.Web.Editors } else { - var defaultUserType = autoLinkOptions.GetDefaultUserType(UmbracoContext, loginInfo); - var userType = Services.UserService.GetUserTypeByAlias(defaultUserType); - if (userType == null) + if (loginInfo.Email.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Email value cannot be null"); + if (loginInfo.ExternalIdentity.Name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null"); + + var autoLinkUser = new BackOfficeIdentityUser { - ViewData[TokenExternalSignInError] = new[] { "Could not auto-link this account, the specified User Type does not exist: " + defaultUserType }; + Email = loginInfo.Email, + Name = loginInfo.ExternalIdentity.Name, + AllowedSections = autoLinkOptions.GetDefaultAllowedSections(UmbracoContext, loginInfo), + Culture = autoLinkOptions.GetDefaultCulture(UmbracoContext, loginInfo), + UserName = loginInfo.Email + }; + + //call the callback if one is assigned + if (autoLinkOptions.OnAutoLinking != null) + { + autoLinkOptions.OnAutoLinking(autoLinkUser, loginInfo); + } + + var userCreationResult = await UserManager.CreateAsync(autoLinkUser); + + if (userCreationResult.Succeeded == false) + { + ViewData[TokenExternalSignInError] = userCreationResult.Errors; } else { - - if (loginInfo.Email.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Email value cannot be null"); - if (loginInfo.ExternalIdentity.Name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null"); - - var autoLinkUser = new BackOfficeIdentityUser() + var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login); + if (linkResult.Succeeded == false) { - Email = loginInfo.Email, - Name = loginInfo.ExternalIdentity.Name, - UserTypeAlias = userType.Alias, - AllowedSections = autoLinkOptions.GetDefaultAllowedSections(UmbracoContext, loginInfo), - Culture = autoLinkOptions.GetDefaultCulture(UmbracoContext, loginInfo), - UserName = loginInfo.Email - }; + ViewData[TokenExternalSignInError] = linkResult.Errors; - //call the callback if one is assigned - if (autoLinkOptions.OnAutoLinking != null) - { - autoLinkOptions.OnAutoLinking(autoLinkUser, loginInfo); - } - - var userCreationResult = await UserManager.CreateAsync(autoLinkUser); - - if (userCreationResult.Succeeded == false) - { - ViewData[TokenExternalSignInError] = userCreationResult.Errors; + //If this fails, we should really delete the user since it will be in an inconsistent state! + var deleteResult = await UserManager.DeleteAsync(autoLinkUser); + if (deleteResult.Succeeded == false) + { + //DOH! ... this isn't good, combine all errors to be shown + ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors); + } } else { - var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login); - if (linkResult.Succeeded == false) - { - ViewData[TokenExternalSignInError] = linkResult.Errors; - - //If this fails, we should really delete the user since it will be in an inconsistent state! - var deleteResult = await UserManager.DeleteAsync(autoLinkUser); - if (deleteResult.Succeeded == false) - { - //DOH! ... this isn't good, combine all errors to be shown - ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors); - } - } - else - { - //sign in - await SignInManager.SignInAsync(autoLinkUser, isPersistent: false, rememberBrowser: false); - } + //sign in + await SignInManager.SignInAsync(autoLinkUser, isPersistent: false, rememberBrowser: false); } } } diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web/Editors/DashboardController.cs index d668399e83..e4c94965b7 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web/Editors/DashboardController.cs @@ -37,13 +37,12 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.InternalServerError); var user = Security.CurrentUser; - var userType = user.UserType.Alias; var allowedSections = string.Join(",", user.AllowedSections); var language = user.Language; var version = UmbracoVersion.GetSemanticVersion().ToSemanticString(); - var url = string.Format(baseUrl + "{0}?section={0}&type={1}&allowed={2}&lang={3}&version={4}", section, userType, allowedSections, language, version); - var key = "umbraco-dynamic-dashboard-" + userType + language + allowedSections.Replace(",", "-") + section; + var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}", section, allowedSections, language, version); + var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section; var content = ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem(key); var result = new JObject(); diff --git a/src/Umbraco.Web/Editors/DashboardSecurity.cs b/src/Umbraco.Web/Editors/DashboardSecurity.cs index a8f5e44ef2..879fd468e0 100644 --- a/src/Umbraco.Web/Editors/DashboardSecurity.cs +++ b/src/Umbraco.Web/Editors/DashboardSecurity.cs @@ -76,33 +76,6 @@ namespace Umbraco.Web.Editors } } - //Check if this item as any grant arguments, if so check if the user is one of the user types approved, if so they will - // be allowed to see it (so far) - if (grantedTypes.Any()) - { - var allApprovedUserTypes = grantedTypes.SelectMany(g => g.Value.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - if (allApprovedUserTypes.InvariantContains(user.UserType.Alias)) - { - allowedSoFar = true; - } - } - else - { - //if there are not explicit grant types then everyone is allowed so far and we'll only disallow on a deny basis - allowedSoFar = true; - } - - //Check if this item as any deny arguments, if so check if the user is one of the user types approved, if so they will - // be denied to see it no matter what - if (denyTypes.Any()) - { - var allDeniedUserTypes = denyTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - if (allDeniedUserTypes.InvariantContains(user.UserType.Alias)) - { - allowedSoFar = false; - } - } - return allowedSoFar; } } diff --git a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs index 8ccfd6dde7..9fb7a63c9f 100644 --- a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs @@ -72,8 +72,7 @@ namespace Umbraco.Web.Editors var userIsAdmin = Umbraco.UmbracoContext.Security.CurrentUser.IsAdmin(); if (userIsAdmin == false) { - var errorMessage = string.Format("User of type {0} is not allowed to toggle the URL tracker", - Umbraco.UmbracoContext.Security.CurrentUser.UserType.Alias); + var errorMessage = "User is not a member of the administrators group and so is not allowed to toggle the URL tracker"; LogHelper.Debug(errorMessage); throw new UserAuthorizationException(errorMessage); } diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs index d27736576f..ada24ee38c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs @@ -21,10 +21,6 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "emailHash")] public string EmailHash { get; set; } - [DataMember(Name = "userType", IsRequired = true)] - [Required] - public string UserType { get; set; } - /// /// Gets/sets the number of seconds for the user's auth ticket to expire /// diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 72093b19a8..213abc5afc 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models.Mapping; @@ -17,7 +18,6 @@ namespace Umbraco.Web.Models.Mapping { config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) - .ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserType.Alias)) .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) @@ -28,7 +28,6 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => user.Id)) - .ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserTypeAlias)) .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture)) @@ -46,7 +45,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id)) .ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections)) .ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name)) - .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => new[] {user.UserType.Alias})) + .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Groups.Select(x => x.Name))) .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId)) .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index f3b0eaeb5f..3cff7344ad 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -229,9 +229,6 @@ namespace Umbraco.Web.Security //if we already have a user object in Umbraco we don't need to do anything, otherwise we need to create a mapped Umbraco account. if (user != null) return user; - //we need to create an Umbraco IUser of a 'writer' type with access to only content - this was how v6 operates. - var writer = _applicationContext.Services.UserService.GetUserTypeByAlias("writer"); - var email = membershipUser.Email; if (email.IsNullOrWhiteSpace()) { @@ -239,7 +236,7 @@ namespace Umbraco.Web.Security email = Guid.NewGuid().ToString("N") + "@example.com"; } - user = new Core.Models.Membership.User(writer) + user = new Core.Models.Membership.User { Email = email, Language = GlobalSettings.DefaultUILanguage, diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a324e39af4..c2b5665f4c 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -476,7 +476,6 @@ - @@ -1856,18 +1855,10 @@ - XmlTree.xsd - - EditUserType.aspx - ASPXCodeBehind - - - EditUserType.aspx - NodePermissions.ascx ASPXCodeBehind @@ -1886,7 +1877,6 @@ PermissionsHandler.asmx - CacheRefresher.asmx @@ -2151,9 +2141,6 @@ ASPXCodeBehind - - ASPXCodeBehind - Form diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserTypes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserTypes.cs deleted file mode 100644 index 54dd21738b..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserTypes.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using umbraco.BusinessLogic; -using System.Collections.Generic; -using umbraco.businesslogic; -using Umbraco.Core; - -namespace umbraco.cms.presentation.Trees -{ - [Tree(Constants.Applications.Users, "userTypes", "User Types", sortOrder: 1)] - public class UserTypes : BaseTree - { - - public UserTypes(string application) : base(application) { } - - public override void RenderJS(ref System.Text.StringBuilder Javascript) - { - Javascript.Append( - @" -function openUserTypes(id) { - UmbClientMgr.contentFrame('users/EditUserType.aspx?id=' + id); -} -"); - } - - public override void Render(ref XmlTree tree) - { - List userTypes = UserType.GetAllUserTypes(); - foreach (UserType userType in userTypes) - { - if (userType.Id > 1) //don't show the admin user type, they should always have full permissions - { - XmlTreeNode node = XmlTreeNode.Create(this); - node.NodeID = userType.Id.ToString(); - node.Action = string.Format("javascript:openUserTypes({0})", userType.Id.ToString()); - node.Icon = "icon-users"; - node.Text = userType.Name; - - OnBeforeNodeRender(ref tree, ref node, EventArgs.Empty); - if (node != null) - { - tree.Add(node); - OnAfterNodeRender(ref tree, ref node, EventArgs.Empty); - } - } - } - } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.Text = ui.Text("user", "userTypes"); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/RelationTypesWebService.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/RelationTypesWebService.asmx.cs index 2b775ed797..2b5965d2f3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/RelationTypesWebService.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/RelationTypesWebService.asmx.cs @@ -1,10 +1,11 @@ using System.Web.Services; using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Web; namespace umbraco.cms.presentation.developer.RelationTypes { - /// + /// /// Webservice to delete relation types, this allows deletion via a javacscript call hooked into the tree UI /// [WebService(Namespace = "http://tempuri.org/")] @@ -22,7 +23,7 @@ namespace umbraco.cms.presentation.developer.RelationTypes { var user = UmbracoContext.Current.Security.CurrentUser; - if (user.UserType.Name == "Administrators") + if (user.IsAdmin()) { var relationService = ApplicationContext.Current.Services.RelationService; var relationType = relationService.GetRelationTypeById(relationTypeId); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs index c02baaaa7b..a310764498 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Data; using System.Configuration; using System.Collections; @@ -70,7 +71,7 @@ namespace umbraco.presentation.dialogs // Translators foreach (var u in BusinessLogic.User.getAll()) - if (u.UserType.Alias.ToLower() == "translator" || UserHasTranslatePermission(u, _currentPage)) + if (u.GetGroups().Select(x => x.ToLower()).Contains("translators") || UserHasTranslatePermission(u, _currentPage)) translator.Items.Add(new ListItem(u.Name, u.Id.ToString())); if (translator.Items.Count == 0) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index 510a526a0e..83c30ddc25 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -93,19 +93,6 @@ namespace umbraco.cms.presentation.user throw new Exception("Admin users can only be edited by admins"); } - // Populate usertype list - foreach (UserType ut in UserType.getAll) - { - if (CurrentUser.IsAdmin() || ut.Alias != "admin") - { - ListItem li = new ListItem(ui.Text("user", ut.Name.ToLower(), UmbracoUser), ut.Id.ToString()); - if (ut.Id == u.UserType.Id) - li.Selected = true; - - userType.Items.Add(li); - } - } - var userCulture = UserExtensions.GetUserCulture(u.Language, Services.TextService); // Populate ui language lsit @@ -467,7 +454,6 @@ namespace umbraco.cms.presentation.user // ok since the membership provider might be storing these details someplace totally different! But we want to keep our UI in sync. u.Name = uname.Text.Trim(); u.Language = userLanguage.SelectedValue; - u.UserType = UserType.GetUserType(int.Parse(userType.SelectedValue)); u.Email = email.Text.Trim(); u.LoginName = lname.Text; u.Disabled = Disabled.Checked; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx deleted file mode 100644 index d8671f0596..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx +++ /dev/null @@ -1,27 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="EditUserType.aspx.cs" MasterPageFile="../masterpages/umbracoPage.Master" - Inherits="umbraco.cms.presentation.user.EditUserType" %> - -<%@ Register TagPrefix="cc2" Namespace="umbraco.uicontrols" Assembly="controls" %> - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.cs deleted file mode 100644 index b6796e45fa..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Collections; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; -using umbraco.BasePages; -using System.Collections.Generic; -using umbraco.interfaces; -using umbraco.BusinessLogic.Actions; -using umbraco.BusinessLogic; -using umbraco.uicontrols; -using umbraco.cms.presentation.Trees; -using Umbraco.Core.IO; - -namespace umbraco.cms.presentation.user -{ - public partial class EditUserType : UmbracoEnsuredPage - { - public EditUserType() - { - CurrentApp = BusinessLogic.DefaultApps.users.ToString(); - } - protected void Page_Load(object sender, EventArgs e) - { - pnlUmbraco.Text = umbraco.ui.Text("usertype", base.getUser()); - - var save = pnlUmbraco.Menu.NewButton(); - save.Click += save_Click; - save.ID = "save"; - save.ToolTip = ui.Text("save"); - save.Text = ui.Text("save"); - - pp_alias.Text = umbraco.ui.Text("usertype", base.getUser()) + " " + umbraco.ui.Text("alias", base.getUser()); - pp_name.Text = umbraco.ui.Text("usertype", base.getUser()) + " " + umbraco.ui.Text("name", base.getUser()); - - pp_rights.Text = umbraco.ui.Text("default", base.getUser()) + " " + umbraco.ui.Text("rights", base.getUser()); - - //ensure we have a query string - if (string.IsNullOrEmpty(Request.QueryString["id"])) - return; - //ensuer it is an integer - if (!int.TryParse(Request.QueryString["id"], out m_userTypeID)) - return; - - if (!IsPostBack) - { - BindActions(); - - ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree(m_userTypeID.ToString(), false); - } - - } - - void save_Click(object sender, EventArgs e) - { - UserType userType = CurrentUserType; - userType.Name = txtUserTypeName.Text; - string actions = ""; - - foreach (ListItem li in cbl_rights.Items) { - if (li.Selected) - actions += li.Value; - } - - userType.DefaultPermissions = actions; - userType.Save(); - - ClientTools.ShowSpeechBubble(speechBubbleIcon.save, ui.Text("speechBubbles", "editUserTypeSaved", base.getUser()), ""); - } - - protected List CurrentUserTypeActions - { - get - { - if (m_userTypeActions == null) - m_userTypeActions = umbraco.BusinessLogic.Actions.Action.FromString(CurrentUserType.DefaultPermissions); - return m_userTypeActions; - } - } - - protected UserType CurrentUserType - { - get - { - if (m_userType == null) - m_userType = UserType.GetUserType(m_userTypeID); - return m_userType; - } - } - private UserType m_userType; - private List m_userTypeActions; - private int m_userTypeID; - - private void BindActions() - { - lblUserTypeAlias.Text = CurrentUserType.Alias; - txtUserTypeName.Text = CurrentUserType.Name; - hidUserTypeID.Value = CurrentUserType.Id.ToString(); - - foreach (IAction ai in global::umbraco.BusinessLogic.Actions.Action.GetPermissionAssignable()) { - - ListItem li = new ListItem(umbraco.ui.Text(ai.Alias, base.getUser()), ai.Letter.ToString()); - - if(CurrentUserTypeActions.Contains(ai)) - li.Selected = true; - - cbl_rights.Items.Add(li); - } - } - - - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.designer.cs deleted file mode 100644 index ac1fddb9ec..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.designer.cs +++ /dev/null @@ -1,105 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.cms.presentation.user { - - - public partial class EditUserType { - - /// - /// pnlUmbraco control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.UmbracoPanel pnlUmbraco; - - /// - /// pnl1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Pane pnl1; - - /// - /// hidUserTypeID control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.HiddenField hidUserTypeID; - - /// - /// pp_name control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel pp_name; - - /// - /// txtUserTypeName control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox txtUserTypeName; - - /// - /// pp_alias control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel pp_alias; - - /// - /// lblUserTypeAlias control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Label lblUserTypeAlias; - - /// - /// pnl2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Pane pnl2; - - /// - /// pp_rights control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel pp_rights; - - /// - /// cbl_rights control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.CheckBoxList cbl_rights; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserTypeTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserTypeTasks.cs deleted file mode 100644 index 0a8e02341a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserTypeTasks.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; -using Umbraco.Web.UI; -using umbraco.interfaces; -using umbraco.BusinessLogic; - -namespace umbraco.cms.presentation.user -{ - public class UserTypeTasks : LegacyDialogTask - { - public override bool PerformSave() - { - try - { - var u = UserType.MakeNew(Alias, "", Alias); - _returnUrl = string.Format("users/EditUserType.aspx?id={0}", u.Id); - return true; - } - catch - { - return false; - } - } - - public override bool PerformDelete() - { - var userType = UserType.GetUserType(ParentID); - if (userType == null) - return false; - userType.Delete(); - return true; - } - - private string _returnUrl = ""; - public override string ReturnUrl - { - get { return _returnUrl; } - } - - public override string AssignedApp - { - get { return DefaultApps.users.ToString(); } - } - } -} diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index 8e7f1f47e1..bf026a5c62 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -293,7 +293,7 @@ namespace umbraco.BasePages AllowedApplications = u.GetApplications().Select(x => x.alias).ToArray(), RealName = u.Name, //currently we only have one user type! - Roles = new[] { u.UserType.Alias }, + Roles = u.GetGroups(), StartContentNode = u.StartNodeId, StartMediaNode = u.StartMediaId, Username = u.LoginName, diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index e8ed675998..f6096306c7 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -313,24 +313,6 @@ namespace umbraco.BusinessLogic return (tmp != null); } - /// - /// Gets or sets the type of the user. - /// - /// The type of the user. - public UserType UserType - { - get - { - if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return new UserType(UserEntity.UserType); - } - set - { - UserEntity.UserType = value.UserTypeItem; - } - } - - /// /// Gets all users /// @@ -447,10 +429,9 @@ namespace umbraco.BusinessLogic /// The full name. /// The login name. /// The password. - /// The user type. - public static User MakeNew(string name, string lname, string passw, UserType ut) + public static User MakeNew(string name, string lname, string passw) { - var user = new Umbraco.Core.Models.Membership.User(name, "", lname, passw, ut.UserTypeItem); + var user = new Umbraco.Core.Models.Membership.User(name, "", lname, passw); ApplicationContext.Current.Services.UserService.Save(user); var u = new User(user); @@ -467,10 +448,9 @@ namespace umbraco.BusinessLogic /// The lname. /// The passw. /// The email. - /// The ut. - public static User MakeNew(string name, string lname, string passw, string email, UserType ut) + public static User MakeNew(string name, string lname, string passw, string email) { - var user = new Umbraco.Core.Models.Membership.User(name, email, lname, passw, ut.UserTypeItem); + var user = new Umbraco.Core.Models.Membership.User(name, email, lname, passw); ApplicationContext.Current.Services.UserService.Save(user); var u = new User(user); @@ -487,8 +467,7 @@ namespace umbraco.BusinessLogic /// The name. /// The lname. /// The email. - /// The ut. - public static void Update(int id, string name, string lname, string email, UserType ut) + public static void Update(int id, string name, string lname, string email) { if (EnsureUniqueLoginName(lname, GetUser(id)) == false) throw new Exception(String.Format("A user with the login '{0}' already exists", lname)); @@ -498,11 +477,10 @@ namespace umbraco.BusinessLogic found.Name = name; found.Username = lname; found.Email = email; - found.UserType = ut.UserTypeItem; ApplicationContext.Current.Services.UserService.Save(found); } - public static void Update(int id, string name, string lname, string email, bool disabled, bool noConsole, UserType ut) + public static void Update(int id, string name, string lname, string email, bool disabled, bool noConsole) { if (EnsureUniqueLoginName(lname, GetUser(id)) == false) throw new Exception(String.Format("A user with the login '{0}' already exists", lname)); @@ -512,7 +490,6 @@ namespace umbraco.BusinessLogic found.Name = name; found.Username = lname; found.Email = email; - found.UserType = ut.UserTypeItem; found.IsApproved = disabled == false; found.IsLockedOut = noConsole; ApplicationContext.Current.Services.UserService.Save(found); @@ -681,7 +658,7 @@ namespace umbraco.BusinessLogic } /// - /// Adds a group to the list groups for the user, ensure to call Save() afterwords + /// Adds a group to the list of groups for the user, ensure to call Save() afterwords /// public void AddGroup(int groupId, string groupName) { @@ -693,6 +670,12 @@ namespace umbraco.BusinessLogic }); } + public string[] GetGroups() + { + if (_lazyId.HasValue) SetupUser(_lazyId.Value); + return UserEntity.Groups.Select(x => x.Name).ToArray(); + } + /// /// Gets or sets a value indicating whether the user has access to the Umbraco back end. diff --git a/src/umbraco.businesslogic/UserType.cs b/src/umbraco.businesslogic/UserType.cs deleted file mode 100644 index b7bc2b9675..0000000000 --- a/src/umbraco.businesslogic/UserType.cs +++ /dev/null @@ -1,259 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Globalization; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Web; -using System.Web.Caching; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using umbraco.DataLayer; - -namespace umbraco.BusinessLogic -{ - /// - /// Represents a umbraco Usertype - /// - [Obsolete("Use the UserService instead")] - public class UserType - { - - internal Umbraco.Core.Models.Membership.IUserType UserTypeItem; - - /// - /// Creates a new empty instance of a UserType - /// - public UserType() - { - UserTypeItem = new Umbraco.Core.Models.Membership.UserType(); - } - - internal UserType(Umbraco.Core.Models.Membership.IUserType userType) - { - UserTypeItem = userType; - } - - /// - /// Creates a new instance of a UserType and attempts to - /// load it's values from the database cache. - /// - /// - /// If the UserType is not found in the existing ID list, then this object - /// will remain an empty object - /// - /// The UserType id to find - public UserType(int id) - { - this.LoadByPrimaryKey(id); - } - - /// - /// Initializes a new instance of the class. - /// - /// The user type id. - /// The name. - public UserType(int id, string name) - { - UserTypeItem = new Umbraco.Core.Models.Membership.UserType(); - UserTypeItem.Id = id; - UserTypeItem.Name = name; - } - - /// - /// Creates a new instance of UserType with all parameters - /// - /// - /// - /// - /// - public UserType(int id, string name, string defaultPermissions, string alias) - { - UserTypeItem = new Umbraco.Core.Models.Membership.UserType(); - UserTypeItem.Id = id; - UserTypeItem.Name = name; - UserTypeItem.Alias = alias; - UserTypeItem.Permissions = defaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); - } - - /// - /// The cache storage for all user types - /// - private static List UserTypes - { - get - { - return ApplicationContext.Current.Services.UserService.GetAllUserTypes() - .Select(x => new UserType(x)) - .ToList(); - } - } - - #region Public Properties - /// - /// Gets or sets the user type alias. - /// - public string Alias - { - get { return UserTypeItem.Alias; } - set { UserTypeItem.Alias = value; } - } - - /// - /// Gets the name of the user type. - /// - public string Name - { - get { return UserTypeItem.Name; } - set { UserTypeItem.Name = value; } - } - - /// - /// Gets the id the user type - /// - public int Id - { - get { return UserTypeItem.Id; } - } - - /// - /// Gets the default permissions of the user type - /// - public string DefaultPermissions - { - get { return UserTypeItem.Permissions == null ? string.Empty : string.Join("", UserTypeItem.Permissions); } - set { UserTypeItem.Permissions = value.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); } - } - - /// - /// Returns an array of UserTypes - /// - [Obsolete("Use the GetAll method instead")] - public static UserType[] getAll - { - get { return GetAllUserTypes().ToArray(); } - } - #endregion - - /// - /// Saves this instance. - /// - public void Save() - { - //ensure that this object has an ID specified (it exists in the database) - if (UserTypeItem.HasIdentity == false) - throw new Exception("The current UserType object does not exist in the database. New UserTypes should be created with the MakeNew method"); - - ApplicationContext.Current.Services.UserService.SaveUserType(UserTypeItem); - - //raise event - OnUpdated(this, new EventArgs()); - } - - /// - /// Deletes this instance. - /// - public void Delete() - { - //ensure that this object has an ID specified (it exists in the database) - if (UserTypeItem.HasIdentity == false) - throw new Exception("The current UserType object does not exist in the database. New UserTypes should be created with the MakeNew method"); - - ApplicationContext.Current.Services.UserService.DeleteUserType(UserTypeItem); - - //raise event - OnDeleted(this, new EventArgs()); - } - - /// - /// Load the data for the current UserType by it's id - /// - /// - /// Returns true if the UserType id was found - /// and the data was loaded, false if it wasn't - public bool LoadByPrimaryKey(int id) - { - UserTypeItem = ApplicationContext.Current.Services.UserService.GetUserTypeById(id); - return UserTypeItem != null; - } - - /// - /// Creates a new user type - /// - /// - /// - /// - public static UserType MakeNew(string name, string defaultPermissions, string alias) - { - //ensure that the current alias does not exist - //get the id for the new user type - var existing = UserTypes.Find(ut => (ut.Alias == alias)); - - if (existing != null) - throw new Exception("The UserType alias specified already exists"); - - var userType = new Umbraco.Core.Models.Membership.UserType - { - Alias = alias, - Name = name, - Permissions = defaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)) - }; - ApplicationContext.Current.Services.UserService.SaveUserType(userType); - - var legacy = new UserType(userType); - - //raise event - OnNew(legacy, new EventArgs()); - - return legacy; - } - - /// - /// Gets the user type with the specied ID - /// - /// The id. - /// - public static UserType GetUserType(int id) - { - return UserTypes.Find(ut => (ut.Id == id)); - } - - /// - /// Returns all UserType's - /// - /// - public static List GetAllUserTypes() - { - return UserTypes; - } - - internal static event TypedEventHandler New; - private static void OnNew(UserType userType, EventArgs args) - { - if (New != null) - { - New(userType, args); - } - } - - internal static event TypedEventHandler Deleted; - private static void OnDeleted(UserType userType, EventArgs args) - { - if (Deleted != null) - { - Deleted(userType, args); - } - } - - internal static event TypedEventHandler Updated; - private static void OnUpdated(UserType userType, EventArgs args) - { - if (Updated != null) - { - Updated(userType, args); - } - } - - } -} \ No newline at end of file diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj b/src/umbraco.businesslogic/umbraco.businesslogic.csproj index 0d6aae1b71..87de399469 100644 --- a/src/umbraco.businesslogic/umbraco.businesslogic.csproj +++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj @@ -242,9 +242,6 @@ Code - - Code - Code diff --git a/src/umbraco.editorControls/tinyMCE3/TinyMCE.cs b/src/umbraco.editorControls/tinyMCE3/TinyMCE.cs index fcae44b113..cf6fba9e43 100644 --- a/src/umbraco.editorControls/tinyMCE3/TinyMCE.cs +++ b/src/umbraco.editorControls/tinyMCE3/TinyMCE.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Linq; using System.Text.RegularExpressions; using System.Web; using System.Web.UI; @@ -136,8 +137,8 @@ namespace umbraco.editorControls.tinyMCE3 config.Add("plugins", _plugins); // Check advanced settings - if (UmbracoEnsuredPage.CurrentUser != null && ("," + _advancedUsers + ",").IndexOf("," + UmbracoEnsuredPage.CurrentUser.UserType.Id + ",") > - -1) + var advancedUserGroupNames = _advancedUsers.Split(','); + if (UmbracoEnsuredPage.CurrentUser != null && advancedUserGroupNames.Intersect(UmbracoEnsuredPage.CurrentUser.GetGroups()).Any()) config.Add("umbraco_advancedMode", "true"); else config.Add("umbraco_advancedMode", "false"); diff --git a/src/umbraco.editorControls/tinymce/tinyMCEPreValueConfigurator.cs b/src/umbraco.editorControls/tinymce/tinyMCEPreValueConfigurator.cs index 533a961416..fe6505be54 100644 --- a/src/umbraco.editorControls/tinymce/tinyMCEPreValueConfigurator.cs +++ b/src/umbraco.editorControls/tinymce/tinyMCEPreValueConfigurator.cs @@ -1,15 +1,15 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Collections; using System.Web.UI; using System.Web.UI.WebControls; - using umbraco.BusinessLogic; using umbraco.DataLayer; +using Umbraco.Core; namespace umbraco.editorControls.tinymce { + + [Obsolete("IDataType and all other references to the legacy property editors are no longer used this will be removed from the codebase in future versions")] public class tinyMCEPreValueConfigurator : System.Web.UI.WebControls.PlaceHolder, interfaces.IDataPrevalue { @@ -178,10 +178,11 @@ namespace umbraco.editorControls.tinymce } // add users - foreach (BusinessLogic.UserType ut in BusinessLogic.UserType.getAll) + var userService = ApplicationContext.Current.Services.UserService; + foreach (var ug in userService.GetAllUserGroups()) { - ListItem li = new ListItem(ut.Name, ut.Id.ToString()); - if (("," + _advancedUsers + ",").IndexOf("," + ut.Id.ToString() + ",") > -1) + ListItem li = new ListItem(ug.Name, ug.Id.ToString()); + if (("," + _advancedUsers + ",").IndexOf("," + ug.Id + ",") > -1) li.Selected = true; _advancedUsersList.Items.Add(li); diff --git a/src/umbraco.providers/UsersMembershipProvider.cs b/src/umbraco.providers/UsersMembershipProvider.cs index 93f5327cbb..39285449a9 100644 --- a/src/umbraco.providers/UsersMembershipProvider.cs +++ b/src/umbraco.providers/UsersMembershipProvider.cs @@ -137,17 +137,11 @@ namespace umbraco.providers try { // Get the usertype of the current user - var ut = UserType.GetUserType(1); - if (BasePages.UmbracoEnsuredPage.CurrentUser != null) - { - ut = BasePages.UmbracoEnsuredPage.CurrentUser.UserType; - } - //ensure the password is encrypted/hashed string salt; var encodedPass = EncryptOrHashNewPassword(password, out salt); - User.MakeNew(username, username, FormatPasswordForStorage(encodedPass, salt), email, ut); + User.MakeNew(username, username, FormatPasswordForStorage(encodedPass, salt), email); status = MembershipCreateStatus.Success; } @@ -468,8 +462,8 @@ namespace umbraco.providers } else { - //This keeps compatibility - even though this logic to update name and user type should not exist here - User.Update(m.Id, typedUser.FullName.Trim(), typedUser.UserName, typedUser.Email, user.IsApproved == false, user.IsLockedOut, typedUser.UserType); + //This keeps compatibility - even though this logic to update name should not exist here + User.Update(m.Id, typedUser.FullName.Trim(), typedUser.UserName, typedUser.Email, user.IsApproved == false, user.IsLockedOut); } m.Save(); @@ -562,7 +556,7 @@ namespace umbraco.providers return new UsersMembershipUser(base.Name, user.LoginName, user.Id, user.Email, string.Empty, string.Empty, true, user.Disabled, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, - DateTime.Now, user.Name, user.Language, user.UserType); + DateTime.Now, user.Name, user.Language); } #endregion diff --git a/src/umbraco.providers/UsersMembershipUser.cs b/src/umbraco.providers/UsersMembershipUser.cs index dd406662dc..ffb2d75ae9 100644 --- a/src/umbraco.providers/UsersMembershipUser.cs +++ b/src/umbraco.providers/UsersMembershipUser.cs @@ -38,17 +38,7 @@ namespace umbraco.providers get { return _Language; } set { _Language = value; } } - private UserType _UserType; - /// - /// Gets or sets the type of the user. - /// - /// The type of the user. - public UserType UserType - { - get { return _UserType; } - set { _UserType = value; } - } #endregion #region Constructors @@ -78,16 +68,14 @@ namespace umbraco.providers /// The last lockout date. /// The full name. /// The language. - /// Type of the user. public UsersMembershipUser(string providerName, string name, object providerUserKey, string email, string passwordQuestion, string comment, bool isApproved, bool isLockedOut, DateTime creationDate, DateTime lastLoginDate, DateTime lastActivityDate, DateTime lastPasswordChangedDate, - DateTime lastLockoutDate, string fullName, string language, UserType userType ) + DateTime lastLockoutDate, string fullName, string language) : base( providerName, name, providerUserKey, email, passwordQuestion, comment, isApproved, isLockedOut, creationDate, lastLoginDate, lastActivityDate, lastPasswordChangedDate, lastLockoutDate) { _FullName = fullName; - _UserType = userType; _Language = language; } #endregion From 92010adce6bdfc7c1555d528437766a2498d81e1 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sat, 29 Oct 2016 08:28:57 +0200 Subject: [PATCH 011/510] Permission checks on nodes for users and groups --- src/Umbraco.Core/Models/Membership/IUser.cs | 9 - src/Umbraco.Core/Models/Membership/User.cs | 11 -- .../AddUserGroupTables.cs | 10 + .../Repositories/PermissionRepository.cs | 1 + .../Repositories/UserRepository.cs | 3 - src/Umbraco.Core/Services/IUserService.cs | 8 - src/Umbraco.Core/Services/SectionService.cs | 1 - src/Umbraco.Core/Services/UserService.cs | 183 +++++++----------- src/Umbraco.Tests/Models/UserTests.cs | 5 - .../Repositories/UserRepositoryTest.cs | 23 --- .../Services/UserServiceTests.cs | 1 - .../TestHelpers/Entities/MockedUser.cs | 1 - src/umbraco.businesslogic/UserGroup.cs | 8 +- 13 files changed, 89 insertions(+), 175 deletions(-) diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index 1fb82116f9..171a9f0c99 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -31,15 +31,6 @@ namespace Umbraco.Core.Models.Membership void SetGroupsLoaded(); - //TODO: This should be a private set - /// - /// The default permission set for the user - /// - /// - /// Currently in umbraco each permission is a single char but with an Enumerable{string} collection this allows for flexible changes to this in the future - /// - IEnumerable DefaultPermissions { get; set; } - IEnumerable AllowedSections { get; } /// diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index d6d02df8f3..8dd1f31654 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -62,8 +62,6 @@ namespace Umbraco.Core.Models.Membership private DateTime _lastLoginDate; private DateTime _lastLockoutDate; - private IEnumerable _defaultPermissions; - private bool _defaultToLiveEditing; private static readonly Lazy Ps = new Lazy(); @@ -259,14 +257,6 @@ namespace Umbraco.Core.Models.Membership set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } } - //TODO: This should be a private set - [DataMember] - public IEnumerable DefaultPermissions - { - get { return _defaultPermissions;} - set { _defaultPermissions = value; } - } - [IgnoreDataMember] internal bool DefaultToLiveEditing { @@ -318,7 +308,6 @@ namespace Umbraco.Core.Models.Membership clone.DisableChangeTracking(); //need to create new collections otherwise they'll get copied by ref clone._groupCollection = new List(_groupCollection.ToList()); - clone._defaultPermissions = new List(_defaultPermissions.ToList()); //re-create the event handler //this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs index dae7505462..603517bbe3 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs @@ -100,6 +100,16 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero ) FROM umbracoUser u"); + // Add the built-in administrator account to all apps + Execute.Sql(@"INSERT INTO umbracoUserGroup2app (userGroupId,app) + SELECT ug.id, app + FROM umbracoUserGroup ug + INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id + INNER JOIN umbracoUser u ON u.id = u2ug.userId + INNER JOIN umbracoUser2app u2a ON u2a.[user] = u.id + WHERE u.id = 0"); + + // Rename some groups for consistency (plural form) Execute.Sql("UPDATE umbracoUserGroup SET userGroupName = 'Writers' WHERE userGroupName = 'Writer'"); Execute.Sql("UPDATE umbracoUserGroup SET userGroupName = 'Translators' WHERE userGroupName = 'Translator'"); } diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index 83c31f6651..b15e11c21a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -104,6 +104,7 @@ namespace Umbraco.Core.Persistence.Repositories whereBuilder.Append(" OR "); } } + whereBuilder.Append(")"); } diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 12bdc12976..dbfb1a5bfe 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -2,13 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Umbraco.Core; using Umbraco.Core.Logging; -using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; - using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index d6df61a7c5..08454c3b86 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -71,14 +71,6 @@ namespace Umbraco.Core.Services /// An enumerable list of IEnumerable GetPermissions(IUser user, params int[] nodeIds); - /// - /// Get permissions set for a user group and optional node ids - /// - /// Group to retrieve permissions for - /// Specifiying nothing will return all group permissions for all nodes - /// An enumerable list of - IEnumerable GetPermissions(IUserGroup group, params int[] nodeIds); - /// /// Gets the permissions for the provided user and path /// diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index b810840873..02cc3f874b 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -184,7 +184,6 @@ namespace Umbraco.Core.Services /// public IEnumerable
GetAllowedSections(int userId) { - var user = _userService.GetUserById(userId); if (user == null) { diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 11a476a10b..66aad86dbd 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -749,86 +749,41 @@ namespace Umbraco.Core.Services uow.Commit(); } - } + } /// - /// Get permissions set for a user and optional node ids + /// Get permissions set for a user and node Id /// - /// If no permissions are found for a particular entity then the user's default permissions will be applied /// User to retrieve permissions for - /// Specifiying nothing will return all user permissions for all nodes + /// Specifiying nothing will return all permissions for all nodes /// An enumerable list of public IEnumerable GetPermissions(IUser user, params int[] nodeIds) { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateUserRepository(uow)) + if (user.GroupsLoaded == false) { - // TODO: rework (to use groups - currently defaulting to user type) - - //if no permissions are assigned to a particular node then we will fill in those permissions with the user's defaults - var result = new List(); - var missingIds = nodeIds.Except(result.Select(x => x.EntityId)); - foreach (var id in missingIds) - { - result.Add( - new EntityPermission( - id, - user.DefaultPermissions.ToArray())); - } - - return result; + throw new InvalidOperationException("Cannot determine permissions for user as their associated groups are not loaded"); } - } - /// - /// Get permissions set for a group and optional node ids - /// - /// Group to retrieve permissions for - /// Specifiying nothing will return all permissions for all nodes - /// An enumerable list of - public IEnumerable GetPermissions(IUserGroup group, params int[] nodeIds) - { - throw new NotImplementedException(); - } - - /// - /// Get permissions set for a user and optional node ids - /// - /// User repository - /// User to retrieve permissions for - /// Specifiying nothing will return all user permissions for all nodes - /// An enumerable list of - private IEnumerable GetPermissions(IUserRepository repository, IUser user, params int[] nodeIds) - { var result = new List(); - - // TODO: rework to groups first, then user type - - // If no permissions are assigned to a particular node then, we will fill in those permissions with the user's defaults - var missingIds = nodeIds.Except(result.Select(x => x.EntityId)); - foreach (var id in missingIds) - { - result.Add( - new EntityPermission( - id, - user.DefaultPermissions.ToArray())); - } - - // Add or amend the existing permissions based on these foreach (var group in user.Groups) { - var groupPermissions = GetPermissions(group, nodeIds).ToList(); - foreach (var groupPermission in groupPermissions) + foreach (var permission in GetPermissions(group, nodeIds)) { - // Add group permission, ensuring we keep a unique value for the entity Id in the list - AddOrAmendPermission(result, groupPermission); + AddOrAmendPermissionList(result, permission); } } return result; } - private void AddOrAmendPermission(IList permissions, EntityPermission groupPermission) + /// + /// For an existing list of , takes a new and aggregates it. + /// If a permission for the entity associated with the new permission already exists, it's updated with those permissions to create a distinct, most permissive set. + /// If it doesn't, it's added to the list. + /// + /// List of already found permissions + /// New permission to aggregate + private void AddOrAmendPermissionList(IList permissions, EntityPermission groupPermission) { var existingPermission = permissions .SingleOrDefault(x => x.EntityId == groupPermission.EntityId); @@ -850,51 +805,38 @@ namespace Umbraco.Core.Services /// String indicating permissions for provided user and path public string GetPermissionsForPath(IUser user, string path) { - var nodeId = GetNodeIdFromPath(path); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateUserRepository(uow)) + if (user.GroupsLoaded == false) { - // Check for permissions directly assigned to the node for the user (if we have any, they take priority - // over anything defined on groups; we'll take the special "null" action as not being defined - // as this is set if someone sets permissions on a user and then removes them) - var permissions = GetPermissions(repository, user, nodeId) - .Where(IsNotNullActionPermission) - .ToList(); - var gotDirectlyAssignedUserPermissions = permissions.Any(); - - // If none found, and checking groups, get permissions from the groups - if (gotDirectlyAssignedUserPermissions == false) - { - foreach (var group in user.Groups) - { - var groupPermissions = GetPermissions(group, nodeId).ToList(); - permissions.AddRange(groupPermissions); - } - } - - // If none found directly on the user, get those defined on the user type - if (gotDirectlyAssignedUserPermissions == false) - { - var typePermissions = GetPermissions(repository, user, nodeId).ToList(); - permissions.AddRange(typePermissions); - } - - // Extract the net permissions from the path from the set of permissions found - string assignedPermissions; - if (TryGetAssignedPermissionsForNode(permissions, nodeId, out assignedPermissions)) - { - return assignedPermissions; - } - - // Exception to everything. If default cruds is empty and we're on root node; allow browse of root node - if (path == "-1") - { - return "F"; - } - - return string.Empty; + throw new InvalidOperationException("Cannot determine permissions for user as their associated groups are not loaded"); } + + var assignedPermissions = GetPermissionsForGroupsAndPath(user.Groups, path); + return GetAggregatePermissions(assignedPermissions); + } + + /// + /// Retrieves the permissions assigned to each group for a given path + /// + /// List of groups associated with the user + /// Path to check permissions for + /// List of strings indicating permissions for each groups + private IEnumerable GetPermissionsForGroupsAndPath(IEnumerable groups, string path) + { + return groups + .Select(g => GetPermissionsForPath(g, path)) + .ToList(); + } + + /// + /// Aggregates a set of permissions strings to return a unique permissions string containing the most permissive set + /// + /// List of permission strings + /// Single permission string + private static string GetAggregatePermissions(IEnumerable assignedPermissions) + { + return string.Join(string.Empty, assignedPermissions + .SelectMany(s => s.ToCharArray()) + .Distinct()); } /// @@ -906,14 +848,14 @@ namespace Umbraco.Core.Services public string GetPermissionsForPath(IUserGroup group, string path) { var nodeId = GetNodeIdFromPath(path); - - var permissions = GetPermissions(group).ToList(); - - string assignedPermissions; - TryGetAssignedPermissionsForNode(permissions, nodeId, out assignedPermissions); - return assignedPermissions; + return string.Join(string.Empty, GetPermissions(group, nodeId).Single().AssignedPermissions); } + /// + /// Parses a path to find the lowermost node id + /// + /// Path as string + /// Node id private static int GetNodeIdFromPath(string path) { return path.Contains(",") @@ -921,6 +863,29 @@ namespace Umbraco.Core.Services : int.Parse(path); } + /// + /// Get permissions set for a group and node Id + /// + /// Group to retrieve permissions for + /// Specifiying nothing will return all permissions for all nodes + /// An enumerable list of + private IEnumerable GetPermissions(IUserGroup group, params int[] nodeIds) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) + { + var explicitPermissions = repository.GetPermissionsForEntities(group.Id, nodeIds); + var result = new List(explicitPermissions); + + // If no permissions are assigned to a particular node then we will fill in those permissions with the group's defaults + var missingIds = nodeIds.Except(result.Select(x => x.EntityId)); + result.AddRange(missingIds + .Select(i => new EntityPermission(i, group.Permissions.ToArray()))); + + return result; + } + } + private static bool IsNotNullActionPermission(EntityPermission x) { const string NullActionChar = "-"; diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index 8699a97698..5bdc842cc1 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -21,7 +21,6 @@ namespace Umbraco.Tests.Models CreateDate = DateTime.Now, Name = "Test", Comments = "comments", - DefaultPermissions = new[]{"a","b","c"}, DefaultToLiveEditing = false, Email = "test@test.com", Language = "en", @@ -48,9 +47,6 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.AllowedSections.Count(), item.AllowedSections.Count()); - Assert.AreNotSame(clone.DefaultPermissions, item.DefaultPermissions); - Assert.AreEqual(clone.DefaultPermissions.Count(), item.DefaultPermissions.Count()); - //Verify normal properties with reflection var allProps = clone.GetType().GetProperties(); foreach (var propertyInfo in allProps) @@ -72,7 +68,6 @@ namespace Umbraco.Tests.Models CreateDate = DateTime.Now, Name = "Test", Comments = "comments", - DefaultPermissions = new[] { "a", "b", "c" }, DefaultToLiveEditing = false, Email = "test@test.com", Language = "en", diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 5c7a87c240..d3ba021294 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -322,33 +322,10 @@ namespace Umbraco.Tests.Persistence.Repositories } } - [Test] - public void Default_User_Permissions_Based_On_User_Type() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - using (var repository = new UserRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax)) - { - - // Act - var user1 = MockedUser.CreateUser("1"); - repository.AddOrUpdate(user1); - unitOfWork.Commit(); - - // Assert - Assert.AreEqual(3, user1.DefaultPermissions.Count()); - Assert.AreEqual("A", user1.DefaultPermissions.ElementAt(0)); - Assert.AreEqual("B", user1.DefaultPermissions.ElementAt(1)); - Assert.AreEqual("C", user1.DefaultPermissions.ElementAt(2)); - } - } - private void AssertPropertyValues(IUser updatedItem, IUser originalUser) { Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id)); Assert.That(updatedItem.Name, Is.EqualTo(originalUser.Name)); - Assert.That(updatedItem.DefaultPermissions, Is.EqualTo(originalUser.DefaultPermissions)); Assert.That(updatedItem.Language, Is.EqualTo(originalUser.Language)); Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved)); Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue)); diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index 4acdff7d2d..b8f23cee6c 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -506,7 +506,6 @@ namespace Umbraco.Tests.Services Assert.IsNotNull(updatedItem); Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id)); Assert.That(updatedItem.Name, Is.EqualTo(originalUser.Name)); - Assert.That(updatedItem.DefaultPermissions, Is.EqualTo(originalUser.DefaultPermissions)); Assert.That(updatedItem.Language, Is.EqualTo(originalUser.Language)); Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved)); Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue)); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs index 9237574c51..53b53e634f 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs @@ -15,7 +15,6 @@ namespace Umbraco.Tests.TestHelpers.Entities Name = "TestUser" + suffix, RawPasswordValue = "testing", IsLockedOut = false, - DefaultPermissions = new[]{"A", "B", "C"}, StartContentId = -1, StartMediaId = -1, Email = "test" + suffix + "@test.com", diff --git a/src/umbraco.businesslogic/UserGroup.cs b/src/umbraco.businesslogic/UserGroup.cs index c04dad96e3..4a18a95c33 100644 --- a/src/umbraco.businesslogic/UserGroup.cs +++ b/src/umbraco.businesslogic/UserGroup.cs @@ -103,7 +103,7 @@ namespace umbraco.BusinessLogic } /// - /// Gets the id the user type + /// Gets the id the user group /// public int Id { @@ -111,7 +111,7 @@ namespace umbraco.BusinessLogic } /// - /// Gets the default permissions of the user type + /// Gets the default permissions of the user group /// public string DefaultPermissions { @@ -238,7 +238,7 @@ namespace umbraco.BusinessLogic public static UserGroup MakeNew(string name, string defaultPermissions, string alias) { //ensure that the current alias does not exist - //get the id for the new user type + //get the id for the new user group var existing = UserGroups.Find(ut => (ut.Alias == alias)); if (existing != null) @@ -261,7 +261,7 @@ namespace umbraco.BusinessLogic } /// - /// Gets the user type with the specied ID + /// Gets the user group with the specfied ID /// /// The id. /// From 93c400a005e36831c04b9047922ce5e1290ae124 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sat, 29 Oct 2016 23:10:00 +0200 Subject: [PATCH 012/510] Fixed and added unit tests for permission checks --- .../Models/Membership/IUserGroup.cs | 2 + .../Models/Membership/UserGroup.cs | 5 ++ .../Repositories/ContentRepository.cs | 8 +-- .../Interfaces/IContentRepository.cs | 6 +-- src/Umbraco.Core/Services/ContentService.cs | 6 +-- src/Umbraco.Core/Services/IContentService.cs | 6 +-- .../Services/IMembershipUserService.cs | 1 + src/Umbraco.Core/Services/SectionService.cs | 2 +- src/Umbraco.Core/Services/UserService.cs | 9 ++-- .../Repositories/ContentRepositoryTest.cs | 24 +++++---- .../Repositories/UserGroupRepositoryTest.cs | 40 +++++++------- .../Repositories/UserRepositoryTest.cs | 42 +++++++++------ .../Services/SectionServiceTests.cs | 53 +++++++------------ .../Services/UserServiceTests.cs | 50 ++++++++++++----- .../TestHelpers/Entities/MockedUserGroup.cs | 27 +++++++--- 15 files changed, 167 insertions(+), 114 deletions(-) diff --git a/src/Umbraco.Core/Models/Membership/IUserGroup.cs b/src/Umbraco.Core/Models/Membership/IUserGroup.cs index 8e9f862910..336caa4c47 100644 --- a/src/Umbraco.Core/Models/Membership/IUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IUserGroup.cs @@ -28,5 +28,7 @@ namespace Umbraco.Core.Models.Membership void RemoveAllowedSection(string sectionAlias); void AddAllowedSection(string sectionAlias); + + void ClearAllowedSections(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index ea5f891037..885a7cbe55 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -93,5 +93,10 @@ namespace Umbraco.Core.Models.Membership _sectionCollection.Add(sectionAlias); } } + + public void ClearAllowedSections() + { + _sectionCollection.Clear(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 1a544cc32e..0a244d30ef 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -780,15 +780,15 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; } /// - /// Assigns a single permission to the current content item for the specified user ids + /// Assigns a single permission to the current content item for the specified user group ids /// /// /// - /// - public void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds) + /// + public void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds) { var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.AssignEntityPermission(entity, permission, userIds); + repo.AssignEntityPermission(entity, permission, groupIds); } public IEnumerable GetPermissionsForEntity(int entityId) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 5f74a21d7a..a0c379666b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -48,12 +48,12 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable GetByPublishedVersion(IQuery query); /// - /// Assigns a single permission to the current content item for the specified user ids + /// Assigns a single permission to the current content item for the specified user group ids /// /// /// - /// - void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds); + /// + void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds); /// /// Gets the list of permissions for the content item diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 8aa7030a8f..b88678ac73 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -111,13 +111,13 @@ namespace Umbraco.Core.Services /// /// /// - /// - public void AssignContentPermission(IContent entity, char permission, IEnumerable userIds) + /// + public void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateContentRepository(uow)) { - repository.AssignEntityPermission(entity, permission, userIds); + repository.AssignEntityPermission(entity, permission, groupIds); } } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 6ee384c413..d2f277dfbb 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -123,12 +123,12 @@ namespace Umbraco.Core.Services void ReplaceContentPermissions(EntityPermissionSet permissionSet); /// - /// Assigns a single permission to the current content item for the specified user ids + /// Assigns a single permission to the current content item for the specified user group ids /// /// /// - /// - void AssignContentPermission(IContent entity, char permission, IEnumerable userIds); + /// + void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds); /// /// Gets the list of permissions for the content item diff --git a/src/Umbraco.Core/Services/IMembershipUserService.cs b/src/Umbraco.Core/Services/IMembershipUserService.cs index 63dc4db37c..4143a871bf 100644 --- a/src/Umbraco.Core/Services/IMembershipUserService.cs +++ b/src/Umbraco.Core/Services/IMembershipUserService.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Services diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index 02cc3f874b..b12412b5f2 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -249,7 +249,7 @@ namespace Umbraco.Core.Services { //delete the assigned applications _uowProvider.GetUnitOfWork().Database.Execute( - "delete from umbracoUser2App where app = @appAlias", + "delete from umbracoUserGroup2App where app = @appAlias", new { appAlias = section.Alias }); //delete the assigned trees diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 66aad86dbd..45dc0c921d 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -878,9 +878,12 @@ namespace Umbraco.Core.Services var result = new List(explicitPermissions); // If no permissions are assigned to a particular node then we will fill in those permissions with the group's defaults - var missingIds = nodeIds.Except(result.Select(x => x.EntityId)); - result.AddRange(missingIds - .Select(i => new EntityPermission(i, group.Permissions.ToArray()))); + var missingIds = nodeIds.Except(result.Select(x => x.EntityId)).ToList(); + if (missingIds.Any()) + { + result.AddRange(missingIds + .Select(i => new EntityPermission(i, group.Permissions.ToArray()))); + } return result; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index abcb5b3d6a..e9b86ee2d3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -1,27 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using System.Xml.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; -using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; - -using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; -using umbraco.editorControls.tinyMCE3; -using umbraco.interfaces; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Persistence.Repositories @@ -67,6 +59,11 @@ namespace Umbraco.Tests.Persistence.Repositories return repository; } + private UserGroupRepository CreateUserGroupRepository(IDatabaseUnitOfWork unitOfWork) + { + return new UserGroupRepository(unitOfWork, CacheHelper, Logger, SqlSyntax); + } + [Test] public void Rebuild_Xml_Structures_With_Non_Latest_Version() { @@ -299,7 +296,14 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; + using (var repository = CreateUserGroupRepository(unitOfWork)) + { + var userGroup = MockedUserGroup.CreateUserGroup("1"); + repository.AddOrUpdate(userGroup); + unitOfWork.Commit(); + } + + ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); @@ -313,7 +317,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); // Act - repository.AssignEntityPermission(parentPage, 'A', new int[] { 0 }); + repository.AssignEntityPermission(parentPage, 'A', new [] { 1 }); var childPage = MockedContent.CreateSimpleContent(contentType, "child", parentPage); repository.AddOrUpdate(childPage); unitOfWork.Commit(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs index a29aeb8c29..6a2412bc63 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs @@ -325,18 +325,24 @@ namespace Umbraco.Tests.Persistence.Repositories // Act //add and remove a few times, this tests the internal collection + groups[0].ClearAllowedSections(); + groups[0].AddAllowedSection("content"); + groups[0].AddAllowedSection("media"); + groups[0].RemoveAllowedSection("content"); + groups[0].AddAllowedSection("content"); groups[0].AddAllowedSection("settings"); - groups[0].AddAllowedSection("settings"); - groups[0].RemoveAllowedSection("settings"); - groups[0].AddAllowedSection("settings"); - - groups[1].AddAllowedSection("developer"); //add the same even though it's already there - groups[2].AddAllowedSection("content"); + groups[0].AddAllowedSection("content"); + + groups[1].ClearAllowedSections(); + groups[1].AddAllowedSection("developer"); + + groups[2].ClearAllowedSections(); repository.AddOrUpdate(groups[0]); repository.AddOrUpdate(groups[1]); + repository.AddOrUpdate(groups[2]); unitOfWork.Commit(); // Assert @@ -345,13 +351,9 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsTrue(result[0].AllowedSections.Contains("content")); Assert.IsTrue(result[0].AllowedSections.Contains("media")); Assert.IsTrue(result[0].AllowedSections.Contains("settings")); - Assert.AreEqual(3, result[1].AllowedSections.Count()); - Assert.IsTrue(result[1].AllowedSections.Contains("content")); - Assert.IsTrue(result[1].AllowedSections.Contains("media")); + Assert.AreEqual(1, result[1].AllowedSections.Count()); Assert.IsTrue(result[1].AllowedSections.Contains("developer")); - Assert.AreEqual(2, result[2].AllowedSections.Count()); - Assert.IsTrue(result[1].AllowedSections.Contains("content")); - Assert.IsTrue(result[1].AllowedSections.Contains("media")); + Assert.AreEqual(0, result[2].AllowedSections.Count()); } } @@ -390,23 +392,23 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var user1 = MockedUserGroup.CreateUserGroup("1", new[] { "media" }); - var user2 = MockedUserGroup.CreateUserGroup("2", new[] { "settings" }); - var user3 = MockedUserGroup.CreateUserGroup("3", new[] { "settings" }); + var user1 = MockedUserGroup.CreateUserGroup("1", allowedSections: new[] { "test1" }); + var user2 = MockedUserGroup.CreateUserGroup("2", allowedSections: new[] { "test2" }); + var user3 = MockedUserGroup.CreateUserGroup("3", allowedSections: new[] { "test1" }); repository.AddOrUpdate(user1); repository.AddOrUpdate(user2); repository.AddOrUpdate(user3); unitOfWork.Commit(); // Act - - var groups = repository.GetGroupsAssignedToSection("test"); + var groups = repository.GetGroupsAssignedToSection("test1"); // Assert Assert.AreEqual(2, groups.Count()); var names = groups.Select(x => x.Name).ToArray(); - Assert.IsTrue(names.Contains("TestGroup1")); - Assert.IsTrue(names.Contains("TestGroup3")); + Assert.IsTrue(names.Contains("TestUserGroup1")); + Assert.IsFalse(names.Contains("TestUserGroup2")); + Assert.IsTrue(names.Contains("TestUserGroup3")); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index d3ba021294..45c21e82c3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -30,10 +30,13 @@ namespace Umbraco.Tests.Persistence.Repositories private UserRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) { - var repository = new UserRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); - return repository; + return new UserRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); } + private UserGroupRepository CreateUserGroupRepository(IDatabaseUnitOfWork unitOfWork) + { + return new UserGroupRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); + } [Test] public void Can_Perform_Add_On_UserRepository() @@ -43,7 +46,6 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var user = MockedUser.CreateUser(); // Act @@ -63,7 +65,6 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var user1 = MockedUser.CreateUser("1"); var use2 = MockedUser.CreateUser("2"); @@ -107,17 +108,14 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) + using (var userGroupRepository = CreateUserGroupRepository(unitOfWork)) { - var user = MockedUser.CreateUser(); - repository.AddOrUpdate(user); - unitOfWork.Commit(); + var user = CreateAndCommitUserWithGroup(repository, userGroupRepository, unitOfWork); // Act var resolved = (User)repository.Get((int)user.Id); resolved.Name = "New Name"; - //the db column is not used, default permissions are taken from the user type's permissions, this is a getter only - //resolved.DefaultPermissions = "ZYX"; resolved.Language = "fr"; resolved.IsApproved = false; resolved.RawPasswordValue = "new"; @@ -134,7 +132,6 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(updatedItem.Id, Is.EqualTo(resolved.Id)); Assert.That(updatedItem.Name, Is.EqualTo(resolved.Name)); - //Assert.That(updatedItem.DefaultPermissions, Is.EqualTo(resolved.DefaultPermissions)); Assert.That(updatedItem.Language, Is.EqualTo(resolved.Language)); Assert.That(updatedItem.IsApproved, Is.EqualTo(resolved.IsApproved)); Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(resolved.RawPasswordValue)); @@ -143,7 +140,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(updatedItem.StartMediaId, Is.EqualTo(resolved.StartMediaId)); Assert.That(updatedItem.Email, Is.EqualTo(resolved.Email)); Assert.That(updatedItem.Username, Is.EqualTo(resolved.Username)); - Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(1)); + Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); + Assert.IsTrue(updatedItem.AllowedSections.Contains("content")); Assert.IsTrue(updatedItem.AllowedSections.Contains("media")); } } @@ -213,13 +211,12 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) + using (var userGroupRepository = CreateUserGroupRepository(unitOfWork)) { - var user = MockedUser.CreateUser(); - repository.AddOrUpdate(user); - unitOfWork.Commit(); + var user = CreateAndCommitUserWithGroup(repository, userGroupRepository, unitOfWork); // Act - var updatedItem = repository.Get((int) user.Id); + var updatedItem = repository.Get((int)user.Id); // Assert AssertPropertyValues(updatedItem, user); @@ -339,6 +336,21 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsTrue(updatedItem.AllowedSections.Contains("content")); } + private static User CreateAndCommitUserWithGroup(IUserRepository repository, IUserGroupRepository userGroupRepository, IDatabaseUnitOfWork unitOfWork) + { + var group = MockedUserGroup.CreateUserGroup(); + userGroupRepository.AddOrUpdate(@group); + unitOfWork.Commit(); + + var user = MockedUser.CreateUser(); + repository.AddOrUpdate(user); + unitOfWork.Commit(); + + userGroupRepository.AddUsersToGroup(@group.Id, new[] { user.Id }); + unitOfWork.Commit(); + return user; + } + private IUser[] CreateAndCommitMultipleUsers(IUserRepository repository, IUnitOfWork unitOfWork) { var user1 = MockedUser.CreateUser("1"); diff --git a/src/Umbraco.Tests/Services/SectionServiceTests.cs b/src/Umbraco.Tests/Services/SectionServiceTests.cs index a300d76e60..5c51233892 100644 --- a/src/Umbraco.Tests/Services/SectionServiceTests.cs +++ b/src/Umbraco.Tests/Services/SectionServiceTests.cs @@ -29,34 +29,20 @@ namespace Umbraco.Tests.Services ServiceContext.SectionService.MakeNew("Settings", "settings", "icon-settings"); ServiceContext.SectionService.MakeNew("Developer", "developer", "icon-developer"); } - [Test] public void SectionService_Can_Get_Allowed_Sections_For_User() { // Arrange - var user = CreateUser(); + var user = CreateTestUser(); // Act var result = ServiceContext.SectionService.GetAllowedSections(user.Id).ToList(); // Assert - Assert.AreEqual(2, result.Count); + Assert.AreEqual(3, result.Count); } - [Test] - public void SectionService_Can_Get_Allowed_Sections_For_User_With_Groups() - { - // Arrange - var user = CreateUser(true); - - // Act - var result = ServiceContext.SectionService.GetAllowedSections(user.Id).ToList(); - - // Assert - Assert.AreEqual(4, result.Count); - } - - private IUser CreateUser(bool withGroups = false) + private IUser CreateTestUser() { var user = new User { @@ -66,26 +52,23 @@ namespace Umbraco.Tests.Services }; ServiceContext.UserService.Save(user, false); - if (withGroups) + var userGroupA = new UserGroup { - var userGroupA = new UserGroup - { - Alias = "GroupA", - Name = "Group A" - }; - userGroupA.AddAllowedSection("media"); - userGroupA.AddAllowedSection("settings"); - ServiceContext.UserService.SaveUserGroup(userGroupA, true, new[] { user.Id }, false); + Alias = "GroupA", + Name = "Group A" + }; + userGroupA.AddAllowedSection("media"); + userGroupA.AddAllowedSection("settings"); + ServiceContext.UserService.SaveUserGroup(userGroupA, true, new[] { user.Id }, false); - var userGroupB = new UserGroup - { - Alias = "GroupB", - Name = "Group B" - }; - userGroupB.AddAllowedSection("settings"); - userGroupB.AddAllowedSection("developer"); - ServiceContext.UserService.SaveUserGroup(userGroupB, true, new[] { user.Id }, false); - } + var userGroupB = new UserGroup + { + Alias = "GroupB", + Name = "Group B" + }; + userGroupB.AddAllowedSection("settings"); + userGroupB.AddAllowedSection("developer"); + ServiceContext.UserService.SaveUserGroup(userGroupB, true, new[] { user.Id }, false); return ServiceContext.UserService.GetUserById(user.Id); } diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index b8f23cee6c..795088f48f 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -39,7 +39,8 @@ namespace Umbraco.Tests.Services { // Arrange var userService = ServiceContext.UserService; - var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); + var user = CreateTestUser(); + var contentType = MockedContentTypes.CreateSimpleContentType(); ServiceContext.ContentTypeService.Save(contentType); var content = new[] @@ -65,7 +66,8 @@ namespace Umbraco.Tests.Services { // Arrange var userService = ServiceContext.UserService; - var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); + var user = CreateTestUser(); + var contentType = MockedContentTypes.CreateSimpleContentType(); ServiceContext.ContentTypeService.Save(contentType); var content = new[] @@ -75,14 +77,12 @@ namespace Umbraco.Tests.Services MockedContent.CreateSimpleContent(contentType) }; ServiceContext.ContentService.Save(content); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { user.Id }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { user.Id }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { user.Id }); - - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { user.Id }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { user.Id }); - - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(2), ActionBrowse.Instance.Letter, new int[] { user.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { 1 }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { 1 }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { 1 }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { 1 }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { 1 }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(2), ActionBrowse.Instance.Letter, new int[] { 1 }); // Act var permissions = userService.GetPermissions(user, content.ElementAt(0).Id, content.ElementAt(1).Id, content.ElementAt(2).Id); @@ -346,6 +346,7 @@ namespace Umbraco.Tests.Services { Id = 1, Alias = "Group1", + Name = "Group 1" }; userGroup.AddAllowedSection("content"); userGroup.AddAllowedSection("mediat"); @@ -390,11 +391,13 @@ namespace Umbraco.Tests.Services { Id = 1, Alias = "Group1", + Name = "Group 2" }; var userGroup2 = new UserGroup { Id = 2, Alias = "Group2", + Name = "Group 2" }; ServiceContext.UserService.SaveUserGroup(userGroup1); ServiceContext.UserService.SaveUserGroup(userGroup2); @@ -422,16 +425,19 @@ namespace Umbraco.Tests.Services { Id = 1, Alias = "Group1", + Name = "Group 1" }; var userGroup2 = new UserGroup { Id = 2, Alias = "Group2", + Name = "Group 2" }; var userGroup3 = new UserGroup { - Id = 2, + Id = 3, Alias = "Group3", + Name = "Group 3" }; ServiceContext.UserService.SaveUserGroup(userGroup1); ServiceContext.UserService.SaveUserGroup(userGroup2); @@ -496,7 +502,7 @@ namespace Umbraco.Tests.Services public void Get_User_By_Username() { // Arrange - var originalUser = (User)ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); + var originalUser = CreateTestUser(); // Act @@ -516,5 +522,25 @@ namespace Umbraco.Tests.Services Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username)); Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); } + + private IUser CreateTestUser() + { + var userGroup = new UserGroup + { + Id = 1, + Alias = "testGroup", + Name = "Test Group", + Permissions = "ABCDEFGHIJ1234567".ToCharArray().Select(x => x.ToString()) + }; + ServiceContext.UserService.SaveUserGroup(userGroup); + ServiceContext.UserService.AddSectionToAllUserGroups("content", 1); + ServiceContext.UserService.AddSectionToAllUserGroups("media", 1); + + var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); + user.AddGroup(userGroup); + user.SetGroupsLoaded(); + ServiceContext.UserService.Save(user); + return user; + } } } diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs index f266f55332..920c84f3c3 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs @@ -4,14 +4,29 @@ namespace Umbraco.Tests.TestHelpers.Entities { public class MockedUserGroup { - internal static UserGroup CreateUserGroup(string suffix = "", string[] permissions = null) + internal static UserGroup CreateUserGroup(string suffix = "", string[] permissions = null, string[] allowedSections = null) { - return new UserGroup() + var group = new UserGroup + { + Alias = "testUserGroup" + suffix, + Name = "TestUserGroup" + suffix, + Permissions = permissions ?? new[] { "A", "B", "C" } + }; + + if (allowedSections == null) + { + group.AddAllowedSection("content"); + group.AddAllowedSection("media"); + } + else + { + foreach (var allowedSection in allowedSections) { - Alias = "testUserGroup" + suffix, - Name = "TestUserGroup" + suffix, - Permissions = permissions ?? new[]{"A", "B", "C"} - }; + group.AddAllowedSection(allowedSection); + } + } + + return group; } } } \ No newline at end of file From 311401d1198d68f7c6fbf2608274356a68ed1391 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sat, 29 Oct 2016 23:57:04 +0200 Subject: [PATCH 013/510] Ensured groups always loaded along with users --- .../Persistence/Repositories/UserRepository.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index dbfb1a5bfe..cad213d1a0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -68,8 +68,15 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoUser.id in (@ids)", new {ids = ids}); } - return ConvertFromDtos(Database.Fetch(sql)) + var users = ConvertFromDtos(Database.Fetch(sql)) .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. + + foreach (var user in users) + { + AssociateGroupsWithUser(user); + } + + return users; } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -84,10 +91,9 @@ namespace Umbraco.Core.Persistence.Repositories var users = ConvertFromDtos(dtos) .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. - // If a single user found (most likely from a look-up by an alternate key like email or username) then populate the groups - if (users.Length == 1) + foreach (var user in users) { - AssociateGroupsWithUser(users[0]); + AssociateGroupsWithUser(user); } return users; From f316b3f6942d6c2b38854d14e7e3b42424a24404 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sat, 29 Oct 2016 23:57:31 +0200 Subject: [PATCH 014/510] Fixed display of node permissions when editing for a group --- .../umbraco/users/PermissionsEditor.js | 14 +++++++------- .../umbraco/users/PermissionsHandler.asmx.cs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/users/PermissionsEditor.js b/src/Umbraco.Web.UI/umbraco/users/PermissionsEditor.js index c193b24394..61a2c716ac 100644 --- a/src/Umbraco.Web.UI/umbraco/users/PermissionsEditor.js +++ b/src/Umbraco.Web.UI/umbraco/users/PermissionsEditor.js @@ -9,11 +9,11 @@ Umbraco.Sys.registerNamespace("Umbraco.Controls"); $.fn.PermissionsEditor = function(opts) { return this.each(function() { var conf = $.extend({ - userId: -1, + groupId: -1, pPanelSelector: "", replacePChkBoxSelector: "" }, opts); - new Umbraco.Controls.PermissionsEditor().init($(this), conf.userId, $(conf.pPanelSelector), conf.replacePChkBoxSelector); + new Umbraco.Controls.PermissionsEditor().init($(this), conf.groupId, $(conf.pPanelSelector), conf.replacePChkBoxSelector); }); }; $.fn.PermissionsEditorAPI = function() { @@ -26,16 +26,16 @@ Umbraco.Sys.registerNamespace("Umbraco.Controls"); /// return { //private members - _userID: -1, + _groupId: -1, _loadingContent: '







', _pPanel: null, _tree: null, _selectedNodes: new Array(), _chkReplaceSelector: null, - init: function(tree, uId, pPanel, chkReplaceSelector) { + init: function(tree, gId, pPanel, chkReplaceSelector) { ///constructor function - this._userID = parseInt(uId); + this._groupId = parseInt(gId); this._pPanel = pPanel; this._tree = tree; this._chkReplaceSelector = chkReplaceSelector; @@ -122,14 +122,14 @@ Umbraco.Sys.registerNamespace("Umbraco.Controls"); }, _showNodePermissions: function(selectedIDs) { var _this = this; - umbraco.cms.presentation.user.PermissionsHandler.GetNodePermissions(this._userID, selectedIDs, function(r) { _this._showNodePermissionsCallback(r); }); + umbraco.cms.presentation.user.PermissionsHandler.GetNodePermissions(this._groupId, selectedIDs, function (r) { _this._showNodePermissionsCallback(r); }); }, _showNodePermissionsCallback: function(result) { this._pPanel.html(result); }, _savePermissions: function(nodeIDs, selectedPermissions, replaceChildren) { var _this = this; - umbraco.cms.presentation.user.PermissionsHandler.SaveNodePermissions(this._userID, nodeIDs, selectedPermissions, replaceChildren, function(r) { _this._savePermissionsHandler(r) }); + umbraco.cms.presentation.user.PermissionsHandler.SaveNodePermissions(this._groupId, nodeIDs, selectedPermissions, replaceChildren, function (r) { _this._savePermissionsHandler(r) }); }, _savePermissionsHandler: function(result) { if (UmbClientMgr.mainWindow().UmbSpeechBubble != null) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx.cs index b7c5e173cf..a570a5c7bc 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx.cs @@ -18,10 +18,10 @@ namespace umbraco.cms.presentation.user [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [ToolboxItem(false)] [ScriptService] - public class GroupPermissionsHandler + public class PermissionsHandler { /// - /// Loads the GroupNodePermissions UserControl with the appropriate properties, renders the contents and returns the output html. + /// Loads the NodePermissions UserControl with the appropriate properties, renders the contents and returns the output html. /// /// [WebMethod] From 6560f38cb09fcb1c0d60e25f8a7237ab804e00a7 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sun, 30 Oct 2016 11:58:36 +0100 Subject: [PATCH 015/510] Fixed caching issues on editing node permissions --- src/Umbraco.Core/Cache/CacheKeys.cs | 2 - src/Umbraco.Core/Services/IUserService.cs | 18 +++- src/Umbraco.Core/Services/UserService.cs | 75 +++++++++------- .../Services/UserServiceTests.cs | 85 +++++++++++++++++-- src/Umbraco.Web/Cache/UserCacheRefresher.cs | 15 +--- .../Cache/UserGroupCacheRefresher.cs | 2 +- .../UserGroupPermissionsCacheRefresher.cs | 2 +- 7 files changed, 143 insertions(+), 56 deletions(-) diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index 596bf2455b..02b01f1e78 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -56,8 +56,6 @@ namespace Umbraco.Core.Cache [EditorBrowsable(EditorBrowsableState.Never)] public const string UserCacheKey = "UmbracoUser"; - public const string UserPermissionsCacheKey = "UmbracoUserPermissions"; - public const string UserGroupPermissionsCacheKey = "UmbracoUserGroupPermissions"; [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 08454c3b86..dafe746fc8 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -71,6 +71,18 @@ namespace Umbraco.Core.Services /// An enumerable list of IEnumerable GetPermissions(IUser user, params int[] nodeIds); + /// + /// Get permissions set for a group and optional node Ids + /// + /// Group to retrieve permissions for + /// + /// Flag indicating if we want to get just the permissions directly assigned for the group and path, + /// or fall back to the group's default permissions when nothing is directly assigned + /// + /// Specifiying nothing will return all permissions for all nodes + /// An enumerable list of + IEnumerable GetPermissions(IUserGroup group, bool directlyAssignedOnly, params int[] nodeIds); + /// /// Gets the permissions for the provided user and path /// @@ -84,8 +96,12 @@ namespace Umbraco.Core.Services ///
/// User to check permissions for /// Path to check permissions for + /// + /// Flag indicating if we want to get just the permissions directly assigned for the group and path, + /// or fall back to the group's default permissions when nothing is directly assigned + /// /// String indicating permissions for provided user and path - string GetPermissionsForPath(IUserGroup group, string path); + string GetPermissionsForPath(IUserGroup group, string path, bool directlyAssignedOnly = true); /// /// Replaces the same permission set for a single group to any number of entities diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 45dc0c921d..81a71e0f05 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -767,7 +767,7 @@ namespace Umbraco.Core.Services var result = new List(); foreach (var group in user.Groups) { - foreach (var permission in GetPermissions(group, nodeIds)) + foreach (var permission in GetPermissions(group, false, nodeIds)) { AddOrAmendPermissionList(result, permission); } @@ -776,6 +776,39 @@ namespace Umbraco.Core.Services return result; } + /// + /// Get permissions set for a group and node Id + /// + /// Group to retrieve permissions for + /// + /// Flag indicating if we want to get just the permissions directly assigned for the group and path, + /// or fall back to the group's default permissions when nothing is directly assigned + /// + /// Specifiying nothing will return all permissions for all nodes + /// An enumerable list of + public IEnumerable GetPermissions(IUserGroup group, bool directlyAssignedOnly, params int[] nodeIds) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) + { + var explicitPermissions = repository.GetPermissionsForEntities(group.Id, nodeIds); + var result = new List(explicitPermissions); + + // If requested, and no permissions are assigned to a particular node, then we will fill in those permissions with the group's defaults + if (directlyAssignedOnly == false) + { + var missingIds = nodeIds.Except(result.Select(x => x.EntityId)).ToList(); + if (missingIds.Any()) + { + result.AddRange(missingIds + .Select(i => new EntityPermission(i, group.Permissions.ToArray()))); + } + } + + return result; + } + } + /// /// For an existing list of , takes a new and aggregates it. /// If a permission for the entity associated with the new permission already exists, it's updated with those permissions to create a distinct, most permissive set. @@ -823,7 +856,7 @@ namespace Umbraco.Core.Services private IEnumerable GetPermissionsForGroupsAndPath(IEnumerable groups, string path) { return groups - .Select(g => GetPermissionsForPath(g, path)) + .Select(g => GetPermissionsForPath(g, path, directlyAssignedOnly: false)) .ToList(); } @@ -844,11 +877,19 @@ namespace Umbraco.Core.Services /// /// User to check permissions for /// Path to check permissions for + /// + /// Flag indicating if we want to get just the permissions directly assigned for the group and path, + /// or fall back to the group's default permissions when nothing is directly assigned + /// /// String indicating permissions for provided user and path - public string GetPermissionsForPath(IUserGroup group, string path) + public string GetPermissionsForPath(IUserGroup group, string path, bool directlyAssignedOnly = true) { var nodeId = GetNodeIdFromPath(path); - return string.Join(string.Empty, GetPermissions(group, nodeId).Single().AssignedPermissions); + var permission = GetPermissions(group, directlyAssignedOnly, nodeId) + .SingleOrDefault(); + return permission != null + ? string.Join(string.Empty, permission.AssignedPermissions) + : string.Empty; } /// @@ -863,32 +904,6 @@ namespace Umbraco.Core.Services : int.Parse(path); } - /// - /// Get permissions set for a group and node Id - /// - /// Group to retrieve permissions for - /// Specifiying nothing will return all permissions for all nodes - /// An enumerable list of - private IEnumerable GetPermissions(IUserGroup group, params int[] nodeIds) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateUserGroupRepository(uow)) - { - var explicitPermissions = repository.GetPermissionsForEntities(group.Id, nodeIds); - var result = new List(explicitPermissions); - - // If no permissions are assigned to a particular node then we will fill in those permissions with the group's defaults - var missingIds = nodeIds.Except(result.Select(x => x.EntityId)).ToList(); - if (missingIds.Any()) - { - result.AddRange(missingIds - .Select(i => new EntityPermission(i, group.Permissions.ToArray()))); - } - - return result; - } - } - private static bool IsNotNullActionPermission(EntityPermission x) { const string NullActionChar = "-"; diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index 795088f48f..8992eebd95 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -93,7 +93,72 @@ namespace Umbraco.Tests.Services Assert.AreEqual(2, permissions.ElementAt(1).AssignedPermissions.Count()); Assert.AreEqual(1, permissions.ElementAt(2).AssignedPermissions.Count()); } - + + [Test] + public void UserService_Get_UserGroup_Assigned_Permissions() + { + // Arrange + var userService = ServiceContext.UserService; + var userGroup = CreateTestUserGroup(); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = new[] + { + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType) + }; + ServiceContext.ContentService.Save(content); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(2), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + + // Act + var permissions = userService.GetPermissions(userGroup, true, content.ElementAt(0).Id, content.ElementAt(1).Id, content.ElementAt(2).Id); + + //assert + Assert.AreEqual(3, permissions.Count()); + Assert.AreEqual(3, permissions.ElementAt(0).AssignedPermissions.Count()); + Assert.AreEqual(2, permissions.ElementAt(1).AssignedPermissions.Count()); + Assert.AreEqual(1, permissions.ElementAt(2).AssignedPermissions.Count()); + } + + [Test] + public void UserService_Get_UserGroup_Assigned_And_Default_Permissions() + { + // Arrange + var userService = ServiceContext.UserService; + var userGroup = CreateTestUserGroup(); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = new[] + { + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType) + }; + ServiceContext.ContentService.Save(content); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + + // Act + var permissions = userService.GetPermissions(userGroup, false, content.ElementAt(0).Id, content.ElementAt(1).Id, content.ElementAt(2).Id); + + //assert + Assert.AreEqual(3, permissions.Count()); + Assert.AreEqual(3, permissions.ElementAt(0).AssignedPermissions.Count()); + Assert.AreEqual(2, permissions.ElementAt(1).AssignedPermissions.Count()); + Assert.AreEqual(17, permissions.ElementAt(2).AssignedPermissions.Count()); + } + [Test] public void Can_Delete_User() { @@ -524,6 +589,17 @@ namespace Umbraco.Tests.Services } private IUser CreateTestUser() + { + var userGroup = CreateTestUserGroup(); + + var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); + user.AddGroup(userGroup); + user.SetGroupsLoaded(); + ServiceContext.UserService.Save(user); + return user; + } + + private UserGroup CreateTestUserGroup() { var userGroup = new UserGroup { @@ -535,12 +611,7 @@ namespace Umbraco.Tests.Services ServiceContext.UserService.SaveUserGroup(userGroup); ServiceContext.UserService.AddSectionToAllUserGroups("content", 1); ServiceContext.UserService.AddSectionToAllUserGroups("media", 1); - - var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); - user.AddGroup(userGroup); - user.SetGroupsLoaded(); - ServiceContext.UserService.Save(user); - return user; + return userGroup; } } } diff --git a/src/Umbraco.Web/Cache/UserCacheRefresher.cs b/src/Umbraco.Web/Cache/UserCacheRefresher.cs index 6f9cc7db80..861d5e6abf 100644 --- a/src/Umbraco.Web/Cache/UserCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserCacheRefresher.cs @@ -2,9 +2,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models.Membership; - using Umbraco.Core.Persistence.Repositories; -using umbraco.interfaces; namespace Umbraco.Web.Cache { @@ -31,8 +29,6 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { ClearAllIsolatedCacheByEntityType(); - if (UserPermissionsCache) - UserPermissionsCache.Result.ClearCacheByKeySearch(CacheKeys.UserPermissionsCacheKey); base.RefreshAll(); } @@ -47,17 +43,8 @@ namespace Umbraco.Web.Cache var userCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); if (userCache) userCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); - - if (UserPermissionsCache) - UserPermissionsCache.Result.ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.UserPermissionsCacheKey, id)); - + base.Remove(id); } - - private Attempt UserPermissionsCache - { - get { return ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); } - } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/UserGroupCacheRefresher.cs b/src/Umbraco.Web/Cache/UserGroupCacheRefresher.cs index 60b5c9a710..5f36f60bab 100644 --- a/src/Umbraco.Web/Cache/UserGroupCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserGroupCacheRefresher.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.Cache private Attempt UserGroupPermissionsCache { - get { return ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); } + get { return ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/UserGroupPermissionsCacheRefresher.cs b/src/Umbraco.Web/Cache/UserGroupPermissionsCacheRefresher.cs index cf091b6618..8ea1b26f47 100644 --- a/src/Umbraco.Web/Cache/UserGroupPermissionsCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserGroupPermissionsCacheRefresher.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Cache private Attempt UserGroupPermissionsCache { - get { return ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); } + get { return ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); } } } } \ No newline at end of file From 8bb3685e5deb3db23736fb82d8a2f4328c7f6532 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sun, 30 Oct 2016 13:53:43 +0100 Subject: [PATCH 016/510] Fixed test failure reported on CI --- src/Umbraco.Core/Services/SectionService.cs | 4 +++- src/Umbraco.Tests/Services/SectionServiceTests.cs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index b12412b5f2..02c0b8c14a 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -211,7 +211,9 @@ namespace Umbraco.Core.Services /// The application icon, which has to be located in umbraco/images/tray folder. public void MakeNew(string name, string alias, string icon) { - MakeNew(name, alias, icon, GetSections().Max(x => x.SortOrder) + 1); + var sections = GetSections(); + var nextSortOrder = sections != null ? GetSections().Max(x => x.SortOrder) + 1 : 1; + MakeNew(name, alias, icon, nextSortOrder); } /// diff --git a/src/Umbraco.Tests/Services/SectionServiceTests.cs b/src/Umbraco.Tests/Services/SectionServiceTests.cs index 5c51233892..556203bb61 100644 --- a/src/Umbraco.Tests/Services/SectionServiceTests.cs +++ b/src/Umbraco.Tests/Services/SectionServiceTests.cs @@ -29,6 +29,7 @@ namespace Umbraco.Tests.Services ServiceContext.SectionService.MakeNew("Settings", "settings", "icon-settings"); ServiceContext.SectionService.MakeNew("Developer", "developer", "icon-developer"); } + [Test] public void SectionService_Can_Get_Allowed_Sections_For_User() { From ece61a45d8fb35546a0be4be03fb2c287eecc378 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sun, 30 Oct 2016 14:16:01 +0100 Subject: [PATCH 017/510] Fixed test failure reported on CI (2) --- src/Umbraco.Core/Services/SectionService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index 02c0b8c14a..87447c06f0 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -212,7 +212,7 @@ namespace Umbraco.Core.Services public void MakeNew(string name, string alias, string icon) { var sections = GetSections(); - var nextSortOrder = sections != null ? GetSections().Max(x => x.SortOrder) + 1 : 1; + var nextSortOrder = sections != null ? sections.Max(x => x.SortOrder) + 1 : 1; MakeNew(name, alias, icon, nextSortOrder); } From d7aba9ce4a80c87c67d6cbfd79b8c1f7a330a624 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sun, 30 Oct 2016 16:10:00 +0100 Subject: [PATCH 018/510] Fixed test failure reported on CI (3) --- src/Umbraco.Core/Services/SectionService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index 87447c06f0..9de793e37d 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -212,7 +212,7 @@ namespace Umbraco.Core.Services public void MakeNew(string name, string alias, string icon) { var sections = GetSections(); - var nextSortOrder = sections != null ? sections.Max(x => x.SortOrder) + 1 : 1; + var nextSortOrder = sections != null && sections.Any() ? sections.Max(x => x.SortOrder) + 1 : 1; MakeNew(name, alias, icon, nextSortOrder); } From bab4c7715d10fa7203c948160ea577caba80c04c Mon Sep 17 00:00:00 2001 From: gmargol Date: Thu, 3 Nov 2016 11:21:53 +0000 Subject: [PATCH 019/510] Adding optional isApproved parameter instead of hardcoding 'true' value --- src/Umbraco.Core/Models/Member.cs | 5 +++-- src/Umbraco.Core/Services/IMembershipMemberService.cs | 3 ++- src/Umbraco.Core/Services/MemberService.cs | 9 +++++---- src/Umbraco.Core/Services/UserService.cs | 7 ++++--- .../Security/Providers/UmbracoMembershipProvider.cs | 4 ++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 02d6b9aece..033bbb49b7 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -96,7 +96,8 @@ namespace Umbraco.Core.Models /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password /// /// - public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType) + /// Optional IsApproved parameter + public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved = true) : base(name, -1, contentType, new PropertyCollection()) { Mandate.ParameterNotNull(contentType, "contentType"); @@ -106,7 +107,7 @@ namespace Umbraco.Core.Models _email = email; _username = username; _rawPasswordValue = rawPasswordValue; - IsApproved = true; + IsApproved = isApproved; } private static readonly Lazy Ps = new Lazy(); diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 801f88d9ac..7419c33254 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -69,8 +69,9 @@ namespace Umbraco.Core.Services /// Email of the to create /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database /// Alias of the Type + /// IsApproved of the to create /// - T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias); + T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved = true); /// /// Gets an by its provider key diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 094539d66e..36bcbf29d2 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -830,10 +830,10 @@ namespace Umbraco.Core.Services /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database /// Alias of the Type /// - IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) + IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved) { var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMemberWithIdentity(username, email, username, passwordValue, memberType); + return CreateMemberWithIdentity(username, email, username, passwordValue, memberType, isApproved); } /// @@ -846,12 +846,13 @@ namespace Umbraco.Core.Services /// Name of the Member to create /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database /// MemberType the Member should be based on + /// Optional IsApproved of the Member to create /// - private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType) + private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType, bool isApproved = true) { if (memberType == null) throw new ArgumentNullException("memberType"); - var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType); + var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved); if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) { diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 8e984d1e5d..13e502fa4f 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -100,7 +100,7 @@ namespace Umbraco.Core.Services /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database /// Alias of the Type /// - IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) + IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved = true) { var userType = GetUserTypeByAlias(memberTypeAlias); if (userType == null) @@ -120,8 +120,9 @@ namespace Umbraco.Core.Services /// Email of the Member to create /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database /// MemberType the Member should be based on + /// Optional IsApproved parameter /// - private IUser CreateUserWithIdentity(string username, string email, string passwordValue, IUserType userType) + private IUser CreateUserWithIdentity(string username, string email, string passwordValue, IUserType userType, bool isApproved = true) { if (userType == null) throw new ArgumentNullException("userType"); @@ -145,7 +146,7 @@ namespace Umbraco.Core.Services StartContentId = -1, StartMediaId = -1, IsLockedOut = false, - IsApproved = true + IsApproved = isApproved }; //adding default sections content and media user.AddAllowedSection("content"); diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index 8482eb72a2..71e2b1d0f3 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -166,11 +166,11 @@ namespace Umbraco.Web.Security.Providers username, email, FormatPasswordForStorage(encodedPassword, salt), - memberTypeAlias); + memberTypeAlias, + isApproved); member.PasswordQuestion = passwordQuestion; member.RawPasswordAnswerValue = EncryptString(passwordAnswer); - member.IsApproved = isApproved; member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; From 26d450ad9a3d9d997cafae93f1651a0881f2ef0b Mon Sep 17 00:00:00 2001 From: gmargol Date: Thu, 3 Nov 2016 11:31:33 +0000 Subject: [PATCH 020/510] Adding optional parameters to tests --- .../UmbracoServiceMembershipProviderTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs index 72900351b2..eb26c9f734 100644 --- a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs +++ b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs @@ -83,10 +83,10 @@ namespace Umbraco.Tests.Membership mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); mServiceMock.Setup(service => service.GetDefaultMemberType()).Returns("Member"); mServiceMock.Setup( - service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m) => + service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m, bool isApproved) => { - createdMember = new Member("test", e, u, p, memberType); + createdMember = new Member("test", e, u, p, memberType, isApproved); }) .Returns(() => createdMember); var provider = new MembersMembershipProvider(mServiceMock.Object); @@ -114,10 +114,10 @@ namespace Umbraco.Tests.Membership mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); mServiceMock.Setup(service => service.GetDefaultMemberType()).Returns("Member"); mServiceMock.Setup( - service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m) => + service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m, bool isApproved) => { - createdMember = new Member("test", e, u, p, memberType); + createdMember = new Member("test", e, u, p, memberType, isApproved); }) .Returns(() => createdMember); @@ -147,10 +147,10 @@ namespace Umbraco.Tests.Membership mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); mServiceMock.Setup(service => service.GetDefaultMemberType()).Returns("Member"); mServiceMock.Setup( - service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m) => + service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, bool a, string p, string m, bool isApproved) => { - createdMember = new Member("test", e, u, p, memberType); + createdMember = new Member("test", e, u, p, memberType, isApproved); }) .Returns(() => createdMember); From 1e73bb12ac82487fc77500b9e0b258ed11593011 Mon Sep 17 00:00:00 2001 From: gmargol Date: Thu, 3 Nov 2016 16:26:14 +0000 Subject: [PATCH 021/510] Fixing failing unit tests --- .../Membership/UmbracoServiceMembershipProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs index eb26c9f734..8ef1265e09 100644 --- a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs +++ b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs @@ -148,7 +148,7 @@ namespace Umbraco.Tests.Membership mServiceMock.Setup(service => service.GetDefaultMemberType()).Returns("Member"); mServiceMock.Setup( service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, bool a, string p, string m, bool isApproved) => + .Callback((string u, string e, string p, string m, bool isApproved) => { createdMember = new Member("test", e, u, p, memberType, isApproved); }) From 2363cdd17b6de24b298e7082ad0c59a0c87e5dd8 Mon Sep 17 00:00:00 2001 From: Shane Marsden Date: Thu, 3 Nov 2016 17:00:36 +0000 Subject: [PATCH 022/510] Painstakingly went through every image file one by one, looking for references, checking if file looked v6ish, checking for constructed filenames etc --- .../Umbraco/Images/pinnedIcons/task_content.ico | Bin 1150 -> 0 bytes .../Umbraco/Images/pinnedIcons/task_default.ico | Bin 1150 -> 0 bytes .../Images/pinnedIcons/task_developer.ico | Bin 1150 -> 0 bytes .../Umbraco/Images/pinnedIcons/task_media.ico | Bin 1150 -> 0 bytes .../Umbraco/Images/pinnedIcons/task_member.ico | Bin 1150 -> 0 bytes .../Images/pinnedIcons/task_settings.ico | Bin 1150 -> 0 bytes .../Umbraco/Images/pinnedIcons/task_users.ico | Bin 1150 -> 0 bytes .../Umbraco/Images/pinnedIcons/umb.ico | Bin 17542 -> 0 bytes src/Umbraco.Web.UI/umbraco/css/background.gif | Bin 151 -> 0 bytes src/Umbraco.Web.UI/umbraco/css/splitter.gif | Bin 88 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/Lminus.png | Bin 219 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/Lplus.png | Bin 224 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/Tminus.png | Bin 207 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/Tplus.png | Bin 222 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/aboutNew.png | Bin 1178 -> 0 bytes .../umbraco/images/actions/sprites.png | Bin 12301 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/arrawBack.gif | Bin 834 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/arrowDown.gif | Bin 832 -> 0 bytes .../umbraco/images/arrowForward.gif | Bin 834 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/audit.png | Bin 897 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/back.png | Bin 422 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/blank.png | Bin 144 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_b.gif | Bin 92 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_b_label.gif | Bin 96 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_bl.gif | Bin 211 -> 0 bytes .../umbraco/images/c_bl_label.gif | Bin 948 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_br.gif | Bin 240 -> 0 bytes .../umbraco/images/c_br_label.gif | Bin 1031 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_r.gif | Bin 93 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_t.gif | Bin 96 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_tl.gif | Bin 242 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_tr.gif | Bin 434 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/collapse.png | Bin 388 -> 0 bytes .../umbraco/images/copy.small.png | Bin 489 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/cut.small.png | Bin 472 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/date.gif | Bin 253 -> 0 bytes .../umbraco/images/delete_button.png | Bin 582 -> 0 bytes .../images/developer/customControlIcon.png | Bin 842 -> 0 bytes .../umbraco/images/developer/pythonIcon.png | Bin 30235 -> 0 bytes .../images/developer/usercontrolIcon.png | Bin 775 -> 0 bytes .../umbraco/images/developer/xsltIcon.png | Bin 776 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/dialogBg.png | Bin 255 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/domain.gif | Bin 362 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/domain_on.png | Bin 472 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/expand.png | Bin 400 -> 0 bytes .../umbraco/images/exportDocumenttype.png | Bin 617 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/false.png | Bin 894 -> 0 bytes .../umbraco/images/find.small.png | Bin 390 -> 0 bytes .../umbraco/images/findDocument.gif | Bin 967 -> 0 bytes .../umbraco/images/findDocument.png | Bin 617 -> 0 bytes .../umbraco/images/folder.small.png | Bin 413 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/forward.png | Bin 418 -> 0 bytes .../umbraco/images/gradientBackground.png | Bin 1321 -> 0 bytes .../umbraco/images/gradientLine.gif | Bin 1482 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/help.gif | Bin 1013 -> 0 bytes .../umbraco/images/htmldoc.small.png | Bin 507 -> 0 bytes .../umbraco/images/importDocumenttype.png | Bin 601 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/loginBg.png | Bin 17566 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/logout.png | Bin 1011 -> 0 bytes .../umbraco/images/logout_small.gif | Bin 996 -> 0 bytes .../umbraco/images/logout_small.png | Bin 592 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/macro.gif | Bin 115 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/new.gif | Bin 246 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/new.png | Bin 1035 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/newStar.gif | Bin 989 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/notepad.png | Bin 489 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/notify.gif | Bin 1003 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/notifyOld.gif | Bin 1003 -> 0 bytes .../umbraco/images/openfoldericon.png | Bin 232 -> 0 bytes .../umbraco/images/options.small.png | Bin 396 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/package2.png | Bin 966 -> 0 bytes .../umbraco/images/paste.small.png | Bin 632 -> 0 bytes .../umbraco/images/permission.gif | Bin 1009 -> 0 bytes .../umbraco/images/permission.png | Bin 581 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/protect.gif | Bin 1023 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/protect.png | Bin 628 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/publish.png | Bin 717 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/refresh.png | Bin 502 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/rollback.gif | Bin 588 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/rollback.png | Bin 626 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/save.png | Bin 549 -> 0 bytes .../umbraco/images/sendToTranslate.png | Bin 1047 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/sort.gif | Bin 388 -> 0 bytes .../umbraco/images/sort.small.png | Bin 400 -> 0 bytes .../umbraco/images/speechBubble/error.gif | Bin 582 -> 0 bytes .../umbraco/images/speechBubble/error.png | Bin 1915 -> 0 bytes .../umbraco/images/speechBubble/info.png | Bin 1542 -> 0 bytes .../umbraco/images/speechBubble/save.gif | Bin 562 -> 0 bytes .../umbraco/images/speechBubble/save.png | Bin 1036 -> 0 bytes .../images/speechBubble/speechbubble.png | Bin 12444 -> 0 bytes .../images/speechBubble/speechbubbleShadow.png | Bin 916 -> 0 bytes .../speechBubble/speechbubbleShadowNew.gif | Bin 1774 -> 0 bytes .../images/speechBubble/speechbubble_shadow.gif | Bin 660 -> 0 bytes .../umbraco/images/speechBubble/success.png | Bin 1036 -> 0 bytes .../umbraco/images/speechBubble/warning.png | Bin 1188 -> 0 bytes .../umbraco/images/thumbnails/developer.png | Bin 19431 -> 0 bytes .../umbraco/images/thumbnails/member.png | Bin 15119 -> 0 bytes .../umbraco/images/thumbnails/memberGroup.png | Bin 19497 -> 0 bytes .../umbraco/images/thumbnails/template.png | Bin 11883 -> 0 bytes .../umbraco/images/thumbnails/xml.png | Bin 12153 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/true.png | Bin 612 -> 0 bytes .../umbraco/images/umbraco/bin_closed.png | Bin 363 -> 0 bytes .../images/umbraco/developerCacheItem.gif | Bin 577 -> 0 bytes .../images/umbraco/developerCacheTypes.gif | Bin 578 -> 0 bytes .../images/umbraco/developerDatatype.gif | Bin 125 -> 0 bytes .../umbraco/images/umbraco/developerMacro.gif | Bin 604 -> 0 bytes .../images/umbraco/developerRegistry.gif | Bin 118 -> 0 bytes .../images/umbraco/developerRegistryItem.gif | Bin 123 -> 0 bytes .../umbraco/images/umbraco/developerXslt.gif | Bin 410 -> 0 bytes .../umbraco/images/umbraco/doc2.gif | Bin 604 -> 0 bytes .../umbraco/images/umbraco/doc3.gif | Bin 616 -> 0 bytes .../umbraco/images/umbraco/doc4.gif | Bin 597 -> 0 bytes .../umbraco/images/umbraco/doc5.gif | Bin 609 -> 0 bytes .../umbraco/images/umbraco/docPic.gif | Bin 607 -> 0 bytes .../umbraco/images/umbraco/mediaMovie.gif | Bin 619 -> 0 bytes .../umbraco/images/umbraco/memberGroup.gif | Bin 1000 -> 0 bytes .../umbraco/images/umbraco/memberType.gif | Bin 1012 -> 0 bytes .../umbraco/images/umbraco/nitros.gif | Bin 525 -> 0 bytes .../umbraco/images/umbraco/package.gif | Bin 1021 -> 0 bytes .../umbraco/images/umbraco/repository.gif | Bin 1062 -> 0 bytes .../umbraco/images/umbraco/settingAgent.gif | Bin 145 -> 0 bytes .../umbraco/images/umbraco/settingCssItem.gif | Bin 299 -> 0 bytes .../images/umbraco/settingDataTypeChild.gif | Bin 144 -> 0 bytes .../umbraco/images/umbraco/settingLanguage.gif | Bin 387 -> 0 bytes .../images/umbraco/settingMasterDatatype.gif | Bin 587 -> 0 bytes .../images/umbraco/settingMasterTemplate.gif | Bin 235 -> 0 bytes .../umbraco/images/umbraco/settingSkin.gif | Bin 1655 -> 0 bytes .../umbraco/images/umbraco/settingTemplate.gif | Bin 252 -> 0 bytes .../umbraco/images/umbraco/settingView.gif | Bin 1644 -> 0 bytes .../umbraco/images/umbraco/settingXML.gif | Bin 268 -> 0 bytes .../umbraco/images/umbraco/settingsScript.gif | Bin 553 -> 0 bytes .../umbraco/images/umbraco/statistik.gif | Bin 305 -> 0 bytes .../umbraco/images/umbraco/uploadpackage.gif | Bin 1035 -> 0 bytes .../umbraco/images/umbraco/user.gif | Bin 998 -> 0 bytes .../umbraco/images/umbraco/userGroup.gif | Bin 1000 -> 0 bytes .../umbraco/images/umbraco/userType.gif | Bin 1012 -> 0 bytes .../Installer/images/bg-bhuiness-cl.gif | Bin 19424 -> 0 bytes .../Installer/images/bg-bhuiness-cr.gif | Bin 46684 -> 0 bytes .../Installer/images/bg-blog-cl.gif | Bin 18564 -> 0 bytes .../Installer/images/bg-blog-cr.gif | Bin 50756 -> 0 bytes .../Installer/images/bg-img-ie.png | Bin 3915 -> 0 bytes .../umbraco_client/Installer/images/bg-img.png | Bin 2452 -> 0 bytes .../Installer/images/bg-normal-cl.gif | Bin 18097 -> 0 bytes .../Installer/images/bg-normal-cr.gif | Bin 46703 -> 0 bytes .../Installer/images/bg-personal-cl.gif | Bin 18097 -> 0 bytes .../Installer/images/bg-personal-cr.gif | Bin 46703 -> 0 bytes .../Installer/images/bg-simple-cl.gif | Bin 17705 -> 0 bytes .../Installer/images/bg-simple-cr.gif | Bin 42137 -> 0 bytes .../Installer/images/btn-blog.png | Bin 22650 -> 0 bytes .../Installer/images/btn-buisness-repeat.png | Bin 16942 -> 0 bytes .../Installer/images/btn-buisness.png | Bin 26481 -> 0 bytes .../Installer/images/btn-confirm.png | Bin 3113 -> 0 bytes .../Installer/images/btn-create-hover.png | Bin 2243 -> 0 bytes .../Installer/images/btn-no-thanks.png | Bin 25962 -> 0 bytes .../Installer/images/btn-personal.png | Bin 30788 -> 0 bytes .../Installer/images/btn-simple.png | Bin 28477 -> 0 bytes .../umbraco_client/Installer/images/img01.jpg | Bin 3189 -> 0 bytes .../umbraco_client/Installer/images/img02.jpg | Bin 3533 -> 0 bytes .../umbraco_client/Installer/images/img03.jpg | Bin 6205 -> 0 bytes .../umbraco_client/Installer/images/img04.jpg | Bin 5029 -> 0 bytes .../umbraco_client/Installer/images/img05.jpg | Bin 6113 -> 0 bytes .../umbraco_client/Installer/images/img06.jpg | Bin 4408 -> 0 bytes .../umbraco_client/Installer/images/img07.jpg | Bin 5924 -> 0 bytes .../umbraco_client/Installer/images/img08.jpg | Bin 4535 -> 0 bytes .../umbraco_client/Installer/images/img09.jpg | Bin 16018 -> 0 bytes .../umbraco_client/Installer/images/img10.jpg | Bin 4173 -> 0 bytes .../umbraco_client/Installer/images/img11.jpg | Bin 6618 -> 0 bytes .../umbraco_client/Installer/images/loader.gif | Bin 2113 -> 0 bytes .../umbraco_client/Installer/images/pbar.gif | Bin 1026 -> 0 bytes .../umbraco_client/Tree/Themes/marker.gif | Bin 61 -> 0 bytes .../umbraco_client/Tree/Themes/marker_rtl.gif | Bin 62 -> 0 bytes .../umbraco_client/Tree/Themes/plus.gif | Bin 52 -> 0 bytes .../umbraco_client/Tree/Themes/remove.png | Bin 720 -> 0 bytes .../umbraco_client/Tree/Themes/rename.png | Bin 3867 -> 0 bytes .../Tree/Themes/umbraco/context.gif | Bin 35 -> 0 bytes .../Tree/Themes/umbraco/create.png | Bin 3329 -> 0 bytes .../umbraco_client/Tree/Themes/umbraco/dot.gif | Bin 43 -> 0 bytes .../umbraco_client/Tree/Themes/umbraco/f.png | Bin 3237 -> 0 bytes .../Tree/Themes/umbraco/fminus.gif | Bin 60 -> 0 bytes .../Tree/Themes/umbraco/fminus_rtl.gif | Bin 59 -> 0 bytes .../Tree/Themes/umbraco/fplus.gif | Bin 108 -> 0 bytes .../Tree/Themes/umbraco/fplus_rtl.gif | Bin 108 -> 0 bytes .../Tree/Themes/umbraco/lastli.gif | Bin 55 -> 0 bytes .../Tree/Themes/umbraco/lastli_rtl.gif | Bin 54 -> 0 bytes .../umbraco_client/Tree/Themes/umbraco/li.gif | Bin 47 -> 0 bytes .../umbraco_client/Tree/sprites_ie6.gif | Bin 7307 -> 0 bytes .../colorpicker/images/custom_background.png | Bin 1916 -> 0 bytes .../colorpicker/images/custom_hex.png | Bin 562 -> 0 bytes .../colorpicker/images/custom_hsb_b.png | Bin 1097 -> 0 bytes .../colorpicker/images/custom_hsb_h.png | Bin 970 -> 0 bytes .../colorpicker/images/custom_hsb_s.png | Bin 1168 -> 0 bytes .../colorpicker/images/custom_indic.gif | Bin 86 -> 0 bytes .../colorpicker/images/custom_rgb_b.png | Bin 1008 -> 0 bytes .../colorpicker/images/custom_rgb_g.png | Bin 1069 -> 0 bytes .../colorpicker/images/custom_rgb_r.png | Bin 1018 -> 0 bytes .../colorpicker/images/custom_submit.png | Bin 997 -> 0 bytes .../colorpicker/images/select.png | Bin 506 -> 0 bytes .../colorpicker/images/select2.png | Bin 518 -> 0 bytes .../colorpicker/images/slider.png | Bin 315 -> 0 bytes .../datepicker/aqua/active-bg.gif | Bin 89 -> 0 bytes .../umbraco_client/datepicker/aqua/dark-bg.gif | Bin 85 -> 0 bytes .../umbraco_client/datepicker/aqua/hover-bg.gif | Bin 89 -> 0 bytes .../datepicker/aqua/menuarrow.gif | Bin 49 -> 0 bytes .../datepicker/aqua/normal-bg.gif | Bin 110 -> 0 bytes .../datepicker/aqua/rowhover-bg.gif | Bin 110 -> 0 bytes .../datepicker/aqua/status-bg.gif | Bin 116 -> 0 bytes .../umbraco_client/datepicker/aqua/theme.css | 2 +- .../umbraco_client/datepicker/aqua/title-bg.gif | Bin 116 -> 0 bytes .../umbraco_client/datepicker/aqua/today-bg.gif | Bin 1122 -> 0 bytes .../datepicker/images/calPickerIcon.png | Bin 674 -> 0 bytes .../datepicker/images/calPickerIconHover.png | Bin 633 -> 0 bytes .../propertypane/images/proppane_bg.png | Bin 554 -> 0 bytes .../scrollingmenu/images/arrawBack.gif | Bin 834 -> 0 bytes .../scrollingmenu/images/arrowForward.gif | Bin 834 -> 0 bytes .../scrollingmenu/images/background.gif | Bin 183 -> 0 bytes .../scrollingmenu/images/button/buttonbg.gif | Bin 273 -> 0 bytes .../images/button/buttonbgdown.gif | Bin 273 -> 0 bytes 217 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_content.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_default.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_developer.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_media.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_member.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_settings.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_users.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/umb.ico delete mode 100644 src/Umbraco.Web.UI/umbraco/css/background.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/css/splitter.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/Lminus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/Lplus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/Tminus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/Tplus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/aboutNew.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/actions/sprites.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/arrawBack.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/arrowDown.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/arrowForward.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/audit.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/back.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/blank.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_b.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_b_label.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_bl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_bl_label.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_br.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_br_label.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_r.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_t.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_tl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_tr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/collapse.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/copy.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/cut.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/date.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/delete_button.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/developer/customControlIcon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/developer/pythonIcon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/developer/usercontrolIcon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/developer/xsltIcon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/dialogBg.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/domain.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/domain_on.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/expand.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/exportDocumenttype.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/false.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/find.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/findDocument.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/findDocument.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/folder.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/forward.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/gradientBackground.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/gradientLine.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/help.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/htmldoc.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/importDocumenttype.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/loginBg.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/logout.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/logout_small.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/logout_small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/macro.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/new.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/new.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/newStar.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/notepad.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/notify.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/notifyOld.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/openfoldericon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/options.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/package2.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/paste.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/permission.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/permission.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/protect.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/protect.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/publish.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/refresh.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/rollback.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/rollback.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/save.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/sendToTranslate.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/sort.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/sort.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/error.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/error.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/info.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/save.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/save.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadow.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadowNew.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_shadow.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/success.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/warning.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/developer.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/member.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/memberGroup.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/template.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/xml.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/true.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/bin_closed.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerCacheItem.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerCacheTypes.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerDatatype.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerMacro.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistry.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistryItem.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerXslt.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/doc2.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/doc3.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/doc4.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/doc5.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/docPic.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/mediaMovie.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/memberGroup.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/memberType.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/nitros.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/package.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/repository.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingAgent.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingCssItem.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingDataTypeChild.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingLanguage.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterDatatype.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterTemplate.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingSkin.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingTemplate.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingView.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingXML.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingsScript.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/statistik.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/uploadpackage.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/user.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/userGroup.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/userType.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-bhuiness-cl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-bhuiness-cr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-blog-cl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-blog-cr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-img-ie.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-img.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-normal-cl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-normal-cr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-personal-cl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-personal-cr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-simple-cl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-simple-cr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-blog.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-buisness-repeat.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-buisness.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-confirm.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-create-hover.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-no-thanks.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-personal.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-simple.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img01.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img02.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img03.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img04.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img05.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img06.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img07.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img08.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img09.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img10.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/img11.jpg delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/loader.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Installer/images/pbar.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/marker.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/marker_rtl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/plus.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/remove.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/rename.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/context.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/create.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/dot.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/f.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fminus.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fminus_rtl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fplus.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fplus_rtl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/lastli.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/lastli_rtl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/li.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/Tree/sprites_ie6.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_background.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_hex.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_hsb_b.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_hsb_h.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_hsb_s.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_indic.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_rgb_b.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_rgb_g.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_rgb_r.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_submit.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/select.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/select2.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/colorpicker/images/slider.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/active-bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/dark-bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/hover-bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/menuarrow.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/normal-bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/rowhover-bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/status-bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/title-bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/today-bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/images/calPickerIcon.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/datepicker/images/calPickerIconHover.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/propertypane/images/proppane_bg.png delete mode 100644 src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/arrawBack.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/arrowForward.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/background.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/button/buttonbg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/button/buttonbgdown.gif diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_content.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_content.ico deleted file mode 100644 index 06aed6d45d10355cfeb8a975db7a6f6b49df0c39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmbW0OH30{6o!XrWXsC6ON}B#V@x1wh-eTEQPeeH%R`OE4bcRQA|?=YgJ?`lj5}8( zrUen&LW*rek(VO0Rglsa%A*wMYwol&)88{wrw|i2PV@ELxw+pt=ib|LTn>Hm@;LfF zz%}gUxcwZ*9VBRqW-{{>{`r`09=|=HFR++u3&R7o1uTo6#`>eIU!R{rM^Tr*)mL+P zu&*XxrzJEu99s|~>ruAhmHE&D_;rPOl~gPQmFPMo7K0?j<#bx!?yEX9tNBCsP9_o| zZ(1Izhh(XPIH-p-sLNoffoQn_(OgaMRglazkVQf6*ByyzzV&`FW8nbg;X8om9pHWo zxZY5FP4N}+5}54*W?rbdMHYvtu8?WIt?3M7+d&9D*T8zNgY{meaU}#pC7AI_2E%1A zLxqZzOkn8liL7sbRKkSFD$>idke9!!NEB6;zCrR&65|jTM%E6+rH=WM<;eKr(pQLqaiqK>gaawM z3kUVA?cdQ>#**3`GrcH#pxUC3+{F4$kA$3-QWzQ z;B>~Nx{AmDw1D@R$lrqF$+;cBy`21QT2J-JlUM%K4NOp7r|KqLCd6iX5t%kZptZJ^ zzx+f5QeXJ-1HfYQF7hnT>ke5aOHk_XtCX?Sgt%`i=(O@EgeO(kG1dMOjzLVQoHJ z?d-T@c0Q}vBzS_UJg;D*G#=@wVD|R%8Czpfr?I|xpH^3ik8`?9{);{2<#w0+2K;90 A{r~^~ diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_default.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_default.ico deleted file mode 100644 index 8ccffe07944d8e8567fb8f3acc09b15dff902982..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmbV}OH30{6o!ZB!mwgtT)Q$Z-4hnX#F&W4Bc+y-b|`HMeFd1(MoOWHh$TQFwY=08 z%0r-3C9;y`v~0CPwe%pF4BreE&K348uf`D<*~^ z_p?m)NrpMYFwA)(5+acwakBmMibNt&jYVo@vpFr?S0X}>H5tvulrJumoco(uokbPb zRG7ZqXA|Rv&yJ-57XnoZJT8;KXG*|bqiEk*z+O7;qpi}P);jd3{KX>vRltLlfEzD| z?qR9_4hHQR7^%v{P$i2#n+P4o1fKG=PhGVki*+f(3U3bB=4|wmUN`55usYsG^Z?5v z_c2r<$6&c!;HeMI(L3_u)V11eRf`tR@dmhOT9MmrCTC5;*5WAECViy78#66Mn5Z}4 ziCrpq2z*H@6dn!VIp2f!xo&u8JJB%Zfu@tgLrWsogOBjcVYn96=P_(7h|F^7j31QV1`GD z;gvCzjW~pJr(9o$DrX<{wPDoMe1g3^gml?9WSP6jV0VxtCO)aFe66+MVta)tI;%;; z-y&?ov=^M;g2c3U80h_qI>OW16_p`KRNrS?I%%djNgj0VL87f(PZ3#J_o~rzASxWsKE23{riE30IOg;Pr-)qdgGL zUFVE|Doec!rD{*`I9i|faCD_g-tZR7$6rh`nLicfsSo{@AbQP{{?8rd4xjVQ3qif% zK$t!4)oc!d<3hsxvNijl=gBk81rPI&?jH^v_yBC-)Ne^L~Q|5S+VA;M4r{`l{^7A1ZF diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_developer.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_developer.ico deleted file mode 100644 index 07bbf289e98427e71132ea77f9e1e8542402c8d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmbV|-)mA~7{}jH^aC0#(t|{}1rZ}6Bt#2RcSI%pRd=%e0p?)PMby>UL@&LZ)MY_k zcp7&7FOtb5>~=f!yZjF$5C|ZZO2Of9!0mP)W)wR9Bc6iAVlkxC>BEc-`7T?R zOT&DjbBYx@_l#JPNCfeCe8}*4Jfk-)%4=*x?vw5ir^yQPqETM=_Zd#76QU?091bJ6 z&0#6;VRCa1#IPFWEfvLjv8=p}Mx(FMlBAf0 zic$)NLTEG^P*oMJRtv$G6+9j>6k^;q%Im&W;wulDJw#tup=lZhg8}B>>|pV`3brYK zBTbLX->7lR^)3GCs>BibtRnSGL*!Kz3+qihtn6a0(ZiXKGT3|h`MCT+Z+45X%zsg_ z^h3k)u7==F4;RWxZ}Nk3?qHu$eY3{D(k!KG5-;ATS?Eqr=zY7Uva(>5+4pbNxRtb| z|I?j9#A)jFjKr^8mXx?&|Ld~V_*t4|Y-SpH@jU4?DNU;W&WnH6@?!TyUerk42|7uA Ql3owU)77UQyvU1x0q}M$Jpcdz diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_media.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_media.ico deleted file mode 100644 index 50e357ca8be25c82e6d4bc4cb3af5118d83a835c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmcJN%WD%+6vl7CKcFkuF8vcMt}06DMnsf=T}c&0BBiN{)@svqQ7HJ>1QlDVP2xDW zF;!|8@c}{w-MbJ=GPT;&*rfBgGo3q#&F8sOg6YE+EY9$o!(7fc-{H*1SQFjN&5WM= z*ttE7?PZMZC+LVy*3PN?w@dFiKFqt1&gjv@wtoK5&UCfgq`6ZcNQ<`3o7E*qVIy!A z7NkNb%yk6x?wCv9UF}v~pXqF|eY=X)@+Z`GK=sIK32M$i`UAEkef)GQCI%wgqI&!d zdhKir=_{eGl)Z@UP~Z2L$)9#r_Yd_>br<@BwiL_A+&BqCkA)PU#8A{?C_L_7N2>1x z`7dtpC%tRpLGl6?-;dzyyStd1N#eunAuQwvh(yT8xA%~~-@CQmvoM`bV5K+--kPhXqLC>#5ei%lf=DGrs=8&GM4q+is&~5|zKY^%FmMfIsGW3T0>b<9K k^2?F3MsuhvY=ZKwHA30ddXC+$=Vvz+$hO9t{tc!54e5t#mjD0& diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_member.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_member.ico deleted file mode 100644 index 7d68fa3c81aa70304ec4667475553019f963a839..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmd6m%WD%s9LGoS;4vr9UIcIc4ZZ70JShl1pq@Nb+n#)!RIsO_(9(lK3sO_lnB9>lq@67&|u`}YbSeR%pu;sIi zoo9?)5{Q(@{K|{&R7Nx!O+_LRgu`Kw!C)|mKp+60&j*jk1Gn1^m&*mG(+NdUh(J2kjUY1AfL}mePVwd zJ#7Q<-NSVE8nAZ*m5(b@PhM#Mze6HbRTul6g_G7$LT(AX#_^|mB`q7%y4+aDD zdOdVHorM?D)49^QQJ8!KpAHr5gmk=(7s8=2eg&v&BpXxtM0oEnLa?ZNM K*uOE?Q2!I-`gEcI diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_settings.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_settings.ico deleted file mode 100644 index 619bdf265d0251a668e34a5bd09ece6c0d51150a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmb7DTT5b56h1iv^CmqF_%@HzuNVaXM-LU*OBZ?w6+gec5w6?Y;oK9!czkmk@1{!=mpLJwp#4gwt$ zVOqMoyRFU5%?5HQ?sw>C61WSf|n(DL~_9UmW)%jMEKJ3DRd?d@7iON-Ii z*f{+wMGU(Ub0yu2gJvSVRk!GZN1=H>jmySwD^ zcy!c4g$E^@%^EK+FTxks>iqm%)SPkE9E*#Kj`j6*Iy^k2bUICnqKN%HJv|0$Wazq1 zcXxMmc6LUrf$KCmIa$Mp!(j*f5!Z8XZ%=4^e0(Io-*3RDg18EOda+m(`B~@q`1sE7 z@UUZabX4f)-0Xq%&CSi}Gcz+P`lM7fLVMum=0?<-b#R`gr6pQjT@||78|!BMv$M0c z`Y|5+z0hE?Chp<={XMCwDm?M`!^4B{%e~{i1cO0yaB#5Jzst)@5AT0_dlNY5ETgU_ z*Mv1ClSy-HYpcxo*Vk8}0Um1Shc)25Ov9T2EgH1i;8)}G`1JI|{m{T+1BZt5V4&x} zonOZD07nBjtjJ>lM+t*?0$0M>v4F=KkH_Ere<}<=@8D8!5_~(U~5_Ba7L76MJM5rch zgsF1&2q~YUE1#O7%S(r)zY6Hc0Btx1y7>s`MlsNG7|^A`)~P^iW)dzA{6Og0G1|%R zE}L9c^vt72dmHG@0v_jp?k0D3oB-W+6u-4%(3(`Bc0UHLS3=ddfRK_AyOY<7!w#ws z#bI@{9hj;EJt7A^m``TT1>KcNW+6SA4w2qU(8dZ}P7~m2a+t))8xz@H#snY9%VaLB zb2GqPD==LQyp{sbbI3d=fuRamKfJyF+TK_ZRnm!k zet2g?JZFoG@N}UWH;3w!i@0jyW^gOd&13ZSorvT8JgimTlbBB?>4&=?$vgF4aziqQ1{NEsBsjWm;&sKD+VwAG?;tx>?w+7 zZ+F7IVu&)qJ6fh<32T>pMK{0@s=*YLTR4#!)t)E+JpoLDp$#U%1usuI@=x9- S!A}N}V|RK|+X74i2m1#JCXm$t diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/umb.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/umb.ico deleted file mode 100644 index b1baa5b622a303eeab112a0b8638a44fac119b77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17542 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 diff --git a/src/Umbraco.Web.UI/umbraco/css/splitter.gif b/src/Umbraco.Web.UI/umbraco/css/splitter.gif deleted file mode 100644 index 4dd2185e91380d33317c877ebd8554bd7b666d0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88 zcmZ?wbhEHbOl4$dXkcKtedFTKAKw*!vM_*v4u}BBFfi%&^shYqmVfb_Ew{Qi-`i8o o+VaR_+Oy7OuTE`y$9?>h&-HJ;&z~Ou{*RxBtNlpQA_fL)0NP?EKHqVn8Lk#c`lIrjj7P;QtIyw;Ol? zc?O;?jv*Ddk_A|pTm=*lC~zFVdQ&MBb@0OtiQr2qf` diff --git a/src/Umbraco.Web.UI/umbraco/images/Lplus.png b/src/Umbraco.Web.UI/umbraco/images/Lplus.png deleted file mode 100644 index 848ec2fc3bbaab6345864c303684ff8a86559cfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*!VDzYUPT51DfSXiUsv`EJiLtR`ZJfMMF533 z3p^r=85p=efH0%e8j~47L4yFF5LY1m|NsB#ebqn)F`$y&;y6$pQ%R6t@PCG<+YP*c zJX22>$B>F!$pS1)t^$e&6gZd#)XV}^6#`leL>0XheB5$;*x1}$U0qr4a0#d>%#nkpi;(?AirP+hi5m^fE-m% z7srr_TgeFwOpI(f46|1#2yiGY`Di?KiU>FVdQ I&MBb@06e2A@c;k- diff --git a/src/Umbraco.Web.UI/umbraco/images/Tplus.png b/src/Umbraco.Web.UI/umbraco/images/Tplus.png deleted file mode 100644 index 2c8d8f4fd38259b2ef70fc63fad505fb0a0f55a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*!VDzYUPT51DfSXiUsv`EJiLtRLI=-jc>;wv z3p^r=85p=efH0%e8j~47L4yFF5LY1m|NsB#ebqn)F`$y&;y6$pQ%R6t@PCG<+YP*c zJY!E6$B>F!$pS1)t^$e&6gZd#)XV}^6#`leL>0XheB5$;*x1}$U0qr4a0#d>%86hy4NT6+w`tp00i_>zopr0L}p{>Hq)$ diff --git a/src/Umbraco.Web.UI/umbraco/images/aboutNew.png b/src/Umbraco.Web.UI/umbraco/images/aboutNew.png deleted file mode 100644 index 94e13801670a834363ede866d1936b4c58b2680d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1178 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m=!WZB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%s|1+P|wiV z#N6CmN5ROz&_Lh7NZ-&%*U;R`*vQJjKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPAEg{v+u2}(t{7puX=A(aKG z`a!A1`K3k4z=%sz23b{L$o& z6x?nBx*mr=^ns4i2SqDVG{b~|X$QoFCt4r}p6pZefN8x5n5e^Uf6!xKU=;9laSW-r zwIuj1Z)!)<~0a62ur9ls7(;O;ow%l$m{$gZheLOdREUX-d?9; z_wRmR$8}8NYrl@uQRDdh!qXxSi)x$>Lw^>&8cJ9cb8qaeJ@SjxIfZRzV=ont9Rtu6s;F0S}}qRi5L*LF+K zIn33aFOnK_N_3Sr3j3P9oN`Oz*-3?K-qi)>vt~5&=52qtsJl%0_7Md}6OIQ~DHZpE z`=Y+dU)Pk-NlNfh)mV1r(o~P+&h*vZt<9%qyGZrbT&e!|{i~|jmlszXM4o0OsQifS pXSAu}zL)&y=8tEFf0gtZ*%%H*HF3EY&Z!5L7M`wtF6*2UngF(!jj#X! diff --git a/src/Umbraco.Web.UI/umbraco/images/actions/sprites.png b/src/Umbraco.Web.UI/umbraco/images/actions/sprites.png deleted file mode 100644 index 8a3ed9b934e88ab3a1ac7c6b75629a865f32c233..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12301 zcmb`tRa6{Mx2;J60fM``ySqbh*8suYwUELC1TS2xaM$1#+#L$n;K72sL+5s%ar!^q zPj`&_H1~d4FKdq_-`r8^s&c4EL`X0&FsKUh(whI28V2U$8Y29^lWT+^+&>}okkRwd zaIyho zs<{tP@-A&}NDMGT{y;TSO||o#I+6-$3`G65{>H$4D8NLG7L=a%Jp@q_Prone6Czxa zg!imMY4XN>;JS;6ea+LRZ?^5w<3p~;ekbUV1WAz;PD9WgW2b>s~Y5Y>VBh z@DjgKp962=-Im}^8{Plx8t2sn_mb6HbX_*ECk=&Q)NBpphY;Bae_Uk&hUDLO|0aT<;r%@_0f-}Bm z89uB?J(j*I;B2bJt@y~A4qz}$&6$=SyM#FXyXe7vW*R|tIAH$_m#p+>1K-kp`MBut zF+wAX%mc})c#xs|LNd(?G2}alArw2#tp1wOMoA}9jGDt84?d59NCavVGrq^QDPe00 z6K${l6m%)*{g(sjmlYK_nLirx++9tmZkbwuf=d3nm-EuXO;2O7CBD&FHCKzU_B;1w zCYhFQm(cEpW>kWXRRV|B76RY!%*nw6+O|V2cIfVC_U$dek8!%|M0*;rc*H4puvurTtRVZ=#@oMnIj+~ z4eO?WF{y<^;yh8Oi3?6V3WtqASWHS1AC;yHE?E64ur8*~g8YRaF@rQwnh2~7j}ZA| zjG2jpSVbcsGr@)mk_MYh7JTSHUs)KG){cs9aj_yaJ>*i$3CKn~sQ-^`Y9ZwOM za<5EZ4DkNY;2=cCD0J@L6jzVXSPd&TB<^-AlZwptb%}chvpftym)~|20uwfU6dG2PhmY9DbpukJjXH8SvRovv-0Pco^v?^pUaDa zxc$|v{s+Tqx13V@uT_R0<2*xv^%Py=^}WpYY*GVBLBG9BSrU8lMg1BM{>Z4jeMd(T z6v;}f$7Iof-C?3QwEh!)qBO9e4HJ5TT<{SAY6TsQf%pePh0;n;WHfq{3s2C|1!TTtaJi-kww$br(Qh#JvQ2Lm?^e8MQ3ESR#DtwPxjJX$$9AQJC- zB#iy>s47Gi$WH1!sG}L*3#9I~H&(&eyowna-^)f?ty4*LYsy1o%dC7FK;Pp-Ll-g9 zv>CuCSyRz36;bnXmAsPDJW@mgnx1yPY;T5`f9f}BG+C@mv1&O#{&qfJ@ChQBCW+V% zmdsd?rB=z70O@erYR(-xvMLQy!`O_U?`{p?5fRFT%xnPw8J{P&7(T=#;w|mE=U22L zDb%}oKSNQJ^=n}e(8<4V9`sL~pwQX$CvZzOziN1rMgd6`#BAEQ;^u&ySmbhfjhZeD%!Vd78MtI@WwbB9oX zEl?8@R}4aUf7^Ww{$<*)ID7ADcv$q6{r>s4C>uieud<#Xv*2M;HdGvCopiid@A(vz zOA=uzt$D9m@$<<{%hk+d4J}RJOw!O;CU$uvB_mUDIgz#sGDb^FdFynKpiPq`2JOU< zmWfIc_x&GKSq&X`Gj}_cuzu%460hgq2-f0|l)7maX@%f^YuIs)o2ZGx4j+USC1SvP z)^9LByF*D`#xFVRvs54X9Eap!t#){~i^!XalR}X`OAG6uH0!gSfpMBZZ4n|Fy3~mp zOO##llJDTZiMXFeEwPyDD&a1fwBTq`)d*7ON%X&ZBnrGE{dySXvk1w+=a-~Wpqf>n zCXC;Me)nwPV+4R>O8g1JC+W~3vmBer(upv|@+t+CN$;hjfg7f_X&?Hz!oe^l$3=@6 zo2wnI@)SG^JbI*F?UOg)_{zDF!pQ`$emn0~WrCc8{g58(5|YHYN3wp7l`2{62@2t8 zntgO@6R3ob{&e@0;|VYhcDSxq_~cJ2x4|~ck6&PgvP4^vU+?$bqq&e0xhHUX%lYz} zw!!4rwDkFB5#zpoYo~Ckpl~_4QUp2KVJDGu(TlsOK$WcA$q8nf*{&ldl+(4n#xmEHF|aaI$r`_E28V`y*}z(R1T9ppI#e~KgW&Z=V3Ky^_z2%@$CbGu_vF(Q^JUA z9_Ho^DQqg3I2V;2H%UN@w3_=8Nzz6N7pa?BE$b6d>v~xsgn}`t=0km6g%w9C z>^^O<&u?<7#K#cM(_Kpp9jyH#&pvC*9m2nWCI>WGXdz@`WI=(VB5M>Lq^w|B*`m0m z&DkC_{00m(+b3!j53!Y21bndLMRt{<+UFC_1h+sAeJYP=5?Ce_bl(1)4yT%Ya$ujz zopinPrRg_XSo{CKDi z-XPw1-SN|%i>D#4rrU)ddOf>o7!>eEV=+~oq-ev_zW!_a;Mzn=D8q44IOEuUR<-4M zrz^OZw)<_NW^Xv?8W>2MeEO`#(^q#WT#SqBjfaERm8eLEK34xRqJIT3P53-jxA}E# zigYcuqmnUM(dxNZiq7-91#|I!*r~1xD3*jr9MEc*j*f!DDMfevR^O0zuxYIsG~&dO za-@FPG&VNIj+GG+38B%L6Zb!Ky-!O^Tb#JUWxhtIJ_5}0ufuSruJUn`KAHRI+)nb9 zDiIKpdUn07R?LP^EEZobx!-;o{qHus!ELoCG$%S6_cg+$W{lQ4VgLBBS|`NW83JVTRl zAN6>^WBUtmG0~d#amsi^|~jo!Nx6IGe{cGVsA?pkukeF(dk0#f&(yd z)Hd5eUEd|#jA@fqY0s{oVP~$!Dt0`Cn)a4w`nPlXjcQi}1=gDhpbg^md1NYcaG8^L zp^trfmxTYL4-z2n*P!mFbcVLypY#?OwW}|0c1tWa)+2n$HxeDt5FU1xQ&2+YiR%wP zb7lyyGjwflitzQ_*mOI@u*Q!1-A|$>bdQ6F^TIk@f4U)0V_x>R7kl03T4(OI{;Iwn z8buZ_dw=7P3rH?LAd}TypummaG&InElsO3b^dKC~^zP<;@ATOyXdH#3zCaJZsa zIv44w{L_~NPbMqj^l>*`cSQ{AqG_t+a^a^F_D@Gj7MBAYxivE(zBc&ZB9qx&^m40X2D?b(!oS^U|HTT| zI+$3R#$;m=xtiD5XWlK~G2EQ_P?}T2lBi9D27xzH?_sfSRdd|Rs%({x_J_5SC2IhL z)@YZ9KYy)$oA+k_H9!jvmW>;ax1`yCkFr`<+1(8?aebK1mZt%xq)4Sr6O?i zI*G3xKYqk#VR5Qkc{fv>RNnhk;^o!RCYIA`w{SS0zU=`kGdPWvyu9l3M^{58vm?Lm zjc+ML7GaiZkzii#oDE5UthA9lS<8Emq$JN<4K_DAtJK%N_bEzRYWPBWb4KpXjOxw% z)T&q^(3i;zl{RR>q3>ekk53@eA@;vE6%NC^dRO0MBLW z5s1J@>BL%?{9)rnTmIR=dXt}5ahHsYbiJNA*vb3P7nP4rF=xdwLCIsv4pU&)@mJV7 z1Sv+lwEYZ6R#y!U<3dq!kXor$5`$kZgp%l^ddQVV*$sCjv!B9Wn+ z4B9v2k`!kzeUr>(rJp@Dr<1{d27Q$rpbtw)16lEmDCWQ)f(u1aWGcC^wxQ|ic#gB# z&5O!BQ!$JmklZ$S6h2gzlbe6na6dHqgX1F>CHnqrk|Z}?UY7?4Z^!Yx%`+ZwVEpD? zLBX09iJymvgrgA7BCl?hN>Uo4^JmS+wq=5U@K8JzRFW~C7hGc0OpGM%Iw^|FzhS&z z<0SoO#YF1qwT4oOCWocQ~oEt=EP06F+EctAF!xDyH4b z5tun`+sbL-02bkAR=*;Gazb5o7xPZAy-hprkr5I68I~If5#jSO)wDd9 zV_p1rkF~Gvpa*C}S@%m>J`p7h{njnm)V#4e13dxW{pXf5SL+cPuRotnm1wqao+(vT z`^XV{PHzjQPlsv?{#5gwF4w_oX=wqyp=82)9tC!zSj$Fi%Z5AzlqhNPle$YVL)M{3 z2D&`_{kj^#u2kD-Dr&;ct=r+h`yTweDlEBvb$Qz&hOCFLT%c&TC!ah^a-!H8@t2a1 zTUVgQ$C;mRbP|vgb3^rQRVh=rdFxn?Yu8S_omU%={$5_Xfv_3Fq#E*U`3d-4tcfYJ zRSUbj#WTaKohPM*V({6~hRo}Tab-C1`C|W6e8Thd^*s{@UTk?dI2E%#aax<-L39G(8lNm02X6ENpsP39h7j#WtSEjOsTO1%I z3DS&uCA^JyYb@A ziaW(iCPAWFMn>7$u*@#hE0gSZNlZ#7(!j^Pbd$|iw_W1r`_t!NGzz@iuE5Rh{s^1M z=n?Go(7Df2`iac{pPuxeo7#UZZ2zZ(u4Ro8y|Vk!BoH+%KP0Mg%jeM>8g|}s>oOq0 zQKl?sK4i%g*V2wK{WuMUVMPP@c=Si^p_d436`qhZlvR{%vFv6o1gMq*G&L6Nbe61j zRMf^QWF;n2ql#FRjcMnMbq8^t40W5A(l*1Dpehkmmp~>~>A4V$=+Ylii;%0RYR!8< zJx!OShDV^8L@$gCAxXOEpbi;ItNIW3evZ`%^vR}`@#vTYtRj9@;}6X29Qz$JMYgM# zG7vRoNJ92N2G^1jokabBZ4x5&CP$F7FlXu)FTS%~knBd~-TN*Rj;bGT^Sh?-H96ia z-_jx(G2zffoiWEKSf{_H+w~|${5^PgR*8+5UO*5KYs>_eP+JNdktJz(VJSi~d!3E1 z4md014?F?y^zEcQvPpT3anw^0C(#g&- z%>9*lPbMA?Zs+F8AWB>;y_JlNP5>#}MRsg)>@lUA#zZR}rkC1>W)%A}#YPksv8#Pz zmp3mlj#3dq)z>iqxX)B0Y;%hBXN`;AdY9#jzGQCcWZtgTTI)f%GGH53R7@?#zF$FqVYj=P{+vE=3(KOL_t1a)S_dTs|jzeE)%dD6y zU(UbSI;j+v^?H}(Ec)zB1~7cR1iK>@`}A8kR*8xG#kDumwb$Bzg?5>zu;>lor+me& z?^yTR8Fj7L^m7+H#aqsL9RKae2k7F_P?eT)Q%OvH?*q(2Bu4)ekaWj!tj6(sy_>zi zG%RVcpZm{6N?RaKPMEs_3D#Q9d(zKdJ{Hso^d)jg`l3XSIiBr{G3G;Lyz^Zj^yR_ z1ThQxyG)s(?8ciB0>-qtJxU01EW=uBi_qB=xNvrsHE<@e$VKwTRC8kwDzD)H?ye&g zNs4`Eu9lQ=8WU~p^)ytZ@m9Kx>AdC=kR0;N#8h$k0PXcGge*xWFi(GO!XU~{9YeW- zbSe)*`x<&IOnC()*K6PHRIc@TxpT{hSRgHQ^8mJ=OMq)JrW|aye2NB#6nX&+(8pzh zlNL3;S~&D^V%s2NVBnZFCmK$IWvF789w3WQ>`*FQSJh4(RSo}{LpM?0%w4I=R`I+@ zeB>k~98zazT@cP{S_azyOF)n*sURbW<8HziM-5K~t)8hq_NfOSc|1{Hkgguys4_ug z))Vu(5tHXaxMoLDjdTL7C}G>a&ohlx8Hs$*wB#gq9*>#40H>V~%j|wm6z*F{DdZ_4 z)e;_t4-&eV9YNUGY&fqt`?9{7k(C9EZTy}OVmP8pC}H{?|Mr;EQD!%9!_vM!Eek5z z?_yzHK6lx}_kVUe;WOy(V&Cu@C!R!J3+gbxUELoW`|C;Ka!f!Aq}Ms_a)RC-liR&cpZe?2#a~fi4-e?2>|y>~r3H|OVXQ+s-Pj*s|3b1@0L*a~wNEWvbC z*@14uYw}Df^9w6JJ)0{- zNLq(dxT6#4xNd;l9IEi{O_23FU2=T zF*6>dLP+mr>+y(YVTc=;ALo(OG8&4C<$-e}ULLcBpF?s<@a!*=|(I7(CrDXb}!U=xoNr zdV0gNkdQetS0VP%2a&15b8AHtaDo=T)*z|gDC(f3#Y~^%N!0WOLd+;?T*hwJ z5C0bK!tRH`8fQkPn%1(m`j;$x>Pb{m2fU|YSTUjHMcU5h-jU#{D5!6KwJBdnSNZ9Y@s_>A}Ndw(WG7R?l(Kt|9p@yI)gS}SxmyeMEKUa zZc72G5-LkehtDp71Khxy4bRQFoc_zhDZ;P+ ziPsTXV*zNw-gVQ-NHypCE{U!oV2H?qVDwE#UsA+&iA6x~yuzlYaxzNT&g|_6iAb8ShPW z&Tb^Ey+tbqp$O5Go__!a%!I6-ljCIV-fvu^;-sX6P=;qsSYEA7`9IdMii8^m(>zB=4=}FSD$J0w(W{i(DtB z$WlNf@#+H{$>l?mhrycQc64;*C}Xyd7WQs)kfMp+`=PDRZb^TA3}h!Je@5m)RYg*4 z5#d)mv9b+)R=nufb8yy+zw!FoUmFCIae;?x#tk&DEsScel1rIP&<<}UrV>peQWzo$ z>eAv*f}*4QsrSox=vXF%2l6?CTWAIpEic0z9!p_O{tIChnf?21*dJ|@B4mSmA` zmHhsKb&77Y*of1zfBMj+RTa}Vg#bylQ*VV|7aG9BnUZn;h9B0+>9FQ!fKZLSxer2? z(v-1P_ZBtztOL;$mTmP3OXkVTSaAC9SYZ>!TDbzmr9~!ocH@^V?DcgM-}_WuET2%p zOKodCVulFrp8DqRme(t5nU$U7LM~42&}|RWs6jN5%VU%C=*g)HMWO6_Q%s%$Dw^U* z*)~-85?7A-h_KiNzx0}KgGDyh18_&f%cow$Y_PJY+{{=sSM^io1^&+>(KhDz|SSe~dU^erm!MJcA|Xu{Aca zMvwb#J^vT~$8AY;(bV*0tY0y$(=)m^S;bqds_q*J5%#Eqe>j(3Sqf0W?90I`D`{wx zTixeGX>K~~m|B~Herxssx1;*~v*J1M%)(J!(?C=4(7v$4KH~}~pL2Rey50Eh1YPlO zn?14P&(^V}@5rG%ik(859x%_dUJo5kt*xB?#eadX&agr*hdin&SqgN`xuqhyXAj!? zb~akYL4?2C5Muez8Dc}@QzhrHxWbW>cmAz?A2-BYowUUcMpxF;yCtp#zhdv9>8ju5 z#FWf3l0a}1_IEI%xADY z4(ZgtEJYMq z4tN|okE4Q8Qh-+V8L6|L7$FM@AK|S9Ifc2~C4VdGPnz0!BH>r5Vc)9c2hE4Gf)&(e zLJIeqewO!6*D`zCClw@OWL3Aa#N#P2qS-^1V=_nSO3D~XKXrAs);huwzwpc>hE->T zG^A-cWiA?BS#JIb4w{PWH<+Z5RW-f2n5eb&J~L==KiIbwiVQfffjxCifLBLgl`s1i zgdx|4PzO-|(xgOh&osQ333bzf8%yt8ZP^v}TC`K`TF6bV5GCH>Di0pO%S;)oa`D4o z$T^_s3_=9f;K%Z^Tg=qsNdF{QclpGT#?~LLb1d0D*XnYwR@9Y0Dpz5&ke^k~n5-dL zdU?}KuXLtnocK6fa2GgI&S5Q7Y4#9Lb@n;Gwj3qX`7%#H@4Hz5{>CX%OjO-g3tsEr zZU^-?2sZ)cn_vUy!cG6AH~A$i7Bd7k_wx9SY7y^y7vR&7y;pzNO+aHaOR!nlvM48b zkvgV(@vm*0+Ca|h6&l*#BetC4pccK<{ zl4eE#s3TpPz>grkv-1ZZX)20qAk757o^+RBqGH&6SBBJq5|7Sd9z!@MJZ|D+;1OgX z8#V|(z`*aD(1`PlILEs(z3&^x3J%>>#bFy~>f+7ikGO=Bb=}tEsf$kXN+Orv1)SHp zuH*mHAiCDY%;gM9XJKa_9!q7u+|P8`iDs_unJJXZtZsdYj!W9vVTu+|o12@nP%)pSy zt?rKbpAT62rys=0Ap(-c$BQ6nDDH+Apojj|=<+*0ZP`dwEWXK7 zDEY@Cs@vM0DiAd@()t;$;p!@U8A(Xu?@QY2f+~dAVaAMX1If^`n%cpmd%okPi#}@@US17fTyI23D8PSh$CtF+ zS*|&e>lV@LzC5|cJ7}~|PfgQdOd0yIM}Q_B-*e#Fe55h!w^_ns_oKDCl6`I_G75F?6uG&m#7X>3ibz*HuvaHy1f--Sljj zfc%*c!jIQ<#(l=OxXT2vP4=ir++ncoUgv+u7?lrtr+Uk`qe{gVfuBgc$lGSKAI`SW zr?XGO^9h&ByT3=wpKswlVvZ<(6k7E%%TEN@A<9`dpz9o#S6*cL8bx0$fCxU>PU zu&?kwY1BApYL+-?>9rk_-C;$CTjvdq#HfvbCu2ks^^ZF;^=o0R}LM^ zYVKWg`;QLUnV5!x@9zs6l3P26(;)#D_-cR9O7cw4IE2su>m^A$NfpBKxhGsX7VB14 zIiNM&gxbryGkKaDY?ZmYKRR4cmw=B#mn@$iVyt3c2NNx*Gm~fZ83x=8>OB}M9PGDl zpVHJ+V-0zD#r!Ls57iDPt^JMhJDl-Z34f<=0H4<uWcqoH|(`!FAe=}=Y#M+UB+Bqivj~+*QKkF#DsN>SaK*j0gEa7C-|C58!<(Zt zw%yJm2SGL>HMJc+aD58tUrq&>S~%)#1rikL(&OV$EU0k8%P}+-IEa-n49pYci)R)B z5HGdEaR}v}!tJpL%O#UMwF-XeXh9RHKkVqFd9!@`QAE*`ow$V#cX4u_^CKCU$)gh7 zBACa|-`gvUiEJYRDS~tII@@F~PD3VuFl?HY0>gI`F|7uY6;d!GRrNnv*i360g%w0g zDHBr)5IN~#>nVKVf%?*01PS3q9eBOcAq>-K)8ikVoqlopLVcn|8L99A-#CC3r~`M3 z!6_-1FrAcP)kolpmLFBw<&@pr2+jW5l2DW2Zq1Ei^+QzEAb#@b2bH{hoSMXEJnAnr zMfQxK!?}#HAJhBJFr|cu%-qXqKCx@;9s%LjN4SE#rmOpkf~0q+r$1SJt=+Mbi%_E? zsi?RcA_?3&I4{{0HNM*y!ew^Y>>@aMTiEGy-zoSx?D(2%sG3nFB2sOy@OPjE(i2~N z*>al3!6oV6(er?bC-uWRAG(t)$2Oi^aSlY=zY zzKV(p%RTwJG&K0;j*linqzmJgM-TowOGUL~#7{W(Zp>%8j*+osiugg!cHit=U2BC) zDKX=A-!Mwu+6`N8@74@D0^c8nK(jM5M?YXwzBN}JzHqL&Mu3fVaM(phl-Gg;L^(TO zw|YZ?p7+eZwB|<9+m(f4Evel8=DbwW0%2=(~bbfkMgX&w?C~;H*!)_!Rd{<)Sl4C~TT2d4#A+?V(R>?U0kNiBpv+TILd&n(|Feo@2uBzobO?YgY@kSzis`WOkY zTaKKZ7aE&Bvl}?RB98OsdKOomy52M7N|y7TNROay8~VchWY%*~hwr!UiV>PUTZ#$E z?zLGJgQv`qEdGg@D#k8r1Fu*TRx5b!DN@Q2-fx(X{;hm7GtS1+*WKQBsJO%gZP}?H zQ#%95+39pfTjyb$ zJ!rmRWYhm%@`#k4Hhf!$fknPi_V~w8?qmzD7%f2#xNeL8Vco`%Aj*`d+? z_C6gi07;7HSzCjWa zg71HYwKd@4&%Svzd6#lFt6)}iG5mJ;uE<1fZqHW{UU(@@1%{}W9EZ=^Z8HcCD5r5? zd&|mm;>U3XEiGPCfq_D&pd#)(efX+un@R^}Se9fXtWF)A!O3m;zDWAy0u}{Kg`^?o z%F6qpf|($y(zpW>x>P*%uiA(@a~zz+vPtqI9l;B?%O>idJwy6wdZ7Z8}(KY52gep~I&v zV=<8ZBnr?;mJUfj#nx@lX)QIYLW1R|wrU%|t))yb!)Qj_0Ousfd})AGV2F(MQ>I`{ zy(WVYmT!p;YK4u*ZQlI-5xzi!>4(8;9Ic;ur&vwv_&kX9Uo#w`Ln7fCFWO*kq+bU~oa};PqqboEo zN2&+|JuS!-OiS@o{yC-6!jQkuK4ZYYvFk2phl1|f#?Ag#4k|{cNYU0~=pD~-;9I8s z;_Sz{(~{#FYI$F6rBZUCqkI9k*VJ?;@wZNOhf)J{W=G& diff --git a/src/Umbraco.Web.UI/umbraco/images/arrawBack.gif b/src/Umbraco.Web.UI/umbraco/images/arrawBack.gif deleted file mode 100644 index 9d3f0ca6869d1af01a28961dd171ec57bc826569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 834 zcmZ?wbhEHbWM>dz_|C)t1poj4hm#=wC>RZa5fcK6KUo+V7?>DzKt2ZL2?h>%238Ik ij|~eBHggCo{R#gdf4GrP-f0R);KHNb68dZ`4AubXbQK2x diff --git a/src/Umbraco.Web.UI/umbraco/images/arrowDown.gif b/src/Umbraco.Web.UI/umbraco/images/arrowDown.gif deleted file mode 100644 index a02ccbf6f88113a7b463dae1a297c8dd96b2eb2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 832 zcmZ?wbhEHb6k%Xz_|5dz_|C)t1poj4hm#=wC>RZa5fcK6KUo+V7?>DzKt2ZL2?h>%238Ik ij|~eBHggC|{nlQ^ zQ9mL`$s(v?QK8*dcWdp=?96;WX1u$V2Jt1&H#6US@B2L8^S+<*swjtqltxRyE#`W0 z?zsl{Jd-nK1XAjr2;!-Za6Z@Ha|iz$&u8Q=oB6@w@R#8GGT=f4UrTmy=H9n^560gf zq96DdssfOJ;CDKui6BtGeH9EL2(I99edW-xF`OHF^y>J(0#uTz5$9ivjW3@j=>u9g zjGfUU_Gg#j?%4ZNrP565&%K&Y38VzRRzuBz1_T9jdb$pSE`DNrpMkX<5;rC-v+}c+ zN+wZUN)Y&xf>eB5035!^-Z+t3FQE+!;KD)Ca=^Vh`Y2KU+4CbUdStk>v-5NF2vUfM zPqYyoh!PBfu*&FaDIr9b#8z}6XO^)h6GL><5bQudJbGp1LZMK|EqWjXu}HWA@JRMT zjUla-V3V5OECpv;v1-E(tn44a{P!tL%Q)&v%jV0ME?%irDw-;}Fc>KaspLN~2K6#j zpQA06MY|bBl~`s&QQUm`6t~ZhVEw=*By*H({zrE{pFcthI5;`xk&%vQxay#0+gMS1 z30gvlDs=1GRh&I>5*@4ikcdUFZf*imqZNh1r`;NPU~p2<>;RgmDs&u%89f1$)+|EB zmDsTB021jmm?h9=#t_nDuq-R3`JC&F6u6C!A`hw_5UN-4Il2W2hGrx6@7#xM^Bq{X z3V3Gs!{gu3N`0ovMjw#ObSu!60L)|xm8y;Mi*a}^m6Q^)ibTi`u#xuIl`^K^Oym3P z6nc7lu4{sE!*v40h}|a-Qf+a(Tb_egxQ`E(h3wz}GUL}_rc)?sYmk39iK*9b5jTAd z4sIRR6jj6M#V3fx!p$yfRzlHHuSLKnZ(#1-1maN>YPksa@f?a@K7h+v96Ne+TYG!E zt2A!Z$kox)wQ4P+s)|^;B&sGjs~fY$xdGc|NydcfI8|hOGWk7w_8#o+&Q3J;{SsgR X9tgLBzw#qC00000NkvXXu0mjfzlxe* diff --git a/src/Umbraco.Web.UI/umbraco/images/back.png b/src/Umbraco.Web.UI/umbraco/images/back.png deleted file mode 100644 index d0ab2e28b5c3d1a1ca487b3cda111a8b488903bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlMCm9|n$4Ua_ z9(cMqhE&{2dce+T=I7^UX3#CK&>(RjhL2B8uS_m)0)qnsBdf(siJ3bp8XM-aYA^^m z@chZ~@$rd~sgY4oTp-oJkn|v(&8zssN5dwT1}_Gtj>cZ@GwH0a6Q4h0=TKntixZgN zFUVmrW2WQ}Mxf;E>D~NnGb$s191abJYX=#bY;1V`FfblDaEwnZF(vKU!-t894?sHW z#1*O@$y09nXht;x5{fj&wF_>sv$e5zyBE-hn#Wc}l t%8V0iiUJWi)AxQ5)n;av>vwZjPqg5g$l+i<$%BubuSQw8RX~x!8UR(mSYiMG diff --git a/src/Umbraco.Web.UI/umbraco/images/c_bl_label.gif b/src/Umbraco.Web.UI/umbraco/images/c_bl_label.gif deleted file mode 100644 index 1f55e5b558aec2344b7a49fa423dad066c8c2d09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 948 zcmZ?wbhEHb(4S}H<0y-dP zgYp6c$4myz2nml33670y!h9|(E+`ysW07~-q9B-bv`t);t0tjw@$o(d`4k_EOAn7U zt1B;y*)b{U^dwf+5R(Z3O)j%c^R-$YFf3!`U{X3TM`0lY!+dt74javbj16oei@c_K zZCDY+#LN^B(4gSh)Z{zWOhG{50Mj})RTj~1)9kYw802g+E-YBc!p6tt(!r3hh=r4# O&%(lTp+QRngEatn=WZ1M diff --git a/src/Umbraco.Web.UI/umbraco/images/c_br.gif b/src/Umbraco.Web.UI/umbraco/images/c_br.gif deleted file mode 100644 index 2674d46df554b311b4b2242a072d52f90e28555d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240 zcmZ?wbhEHb6krfwIKsei@Zj!$|NhOGId%X3owsk_nlfeLgr1&-b7pVYxaslZTZKfd+p)63hpFTZ{J zbpQUH2M_LEv10L*DHHqqdloHPaQpVH&6_uMb#}D1wk(`8dqPjo|NsC0{rkr-3PwX< zsD*&yPZmZ721y1Tki$WFf`Q`+gFc6hh5!>QJFf(f!GZ<_$7T*uqY?{&q(iN|nsFO6 z43%B`Buw*cDhe05O;EF+^rphm;q-K-fT|LW;FPnIwVfaJtO!;;KgFb&saB&z?c#hc zgN&L4#zRbuoLmAs7KFS!z{#)RAt0dW%(jY8-p64_!huF6$u)d64vkI?-RygUa&DJo zGcgxk-!}Kdw@j8T@%lb9E_^!}y}M#B-@V@poSN6BF{VrWIpD~^!7HZm=k?XqPq}8O z8Z?~RS=`3PppqeGpl~qqa-3vOTJ80Ql*?;{cWmRm{e_WvTmJWR3mO_eKHR{}#9$2o Di-wo} diff --git a/src/Umbraco.Web.UI/umbraco/images/c_r.gif b/src/Umbraco.Web.UI/umbraco/images/c_r.gif deleted file mode 100644 index c3976c6282cd5d4f4ceb63126db58a44f8ed8b49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93 zcmZ?wbhEHb6lGv!*vtR||NsBLeC6!pCwJbyeR}ZV?iDK*Pnj}t(V_)ytt~()uo48J g_>+Z^fq{!b2P6bCgMo!hU}U>Z0gLM-ng9R* diff --git a/src/Umbraco.Web.UI/umbraco/images/c_tl.gif b/src/Umbraco.Web.UI/umbraco/images/c_tl.gif deleted file mode 100644 index 836446e39b130faaf123cebd4f3ac99d56cfe586..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 242 zcmZ?wbhEHbJ`*! z!*}XYlh8-0Ez=5bdW(6U_`u$pH23yXf%v0#^$rS3BK1uIb)rfFqFr5WRoy*ZO8q@N Z?PC2?`9*o`c;?L$<%d;Iv;+qdsNfBtyl`0B znKE(a%$fW5?|l07?$4h;Z{NQ4?c2A{pFjWm_wV-Y%ZnB*c>m$~%U6&3`+L5A{d)S$ zvG3o%fBN+D|Ns9C*l@{j7$Wm0)=*dx16w?%9;+y2TaM6-w;+pD0!ZMxl8jBW9U$jMvf1a|Z#<62d zj%`s_VbEmVbzDO$M?vGX3g3Bmg==>;Jn!B(SFft|{JDasmcor&7oNR%p7Z83(-Zek tpLL!;zWq$@$=c5e{PGU`9JU)cU+~LuGOEbA&GC42q_bM{6e}ZxH2}a8%Dw;q diff --git a/src/Umbraco.Web.UI/umbraco/images/collapse.png b/src/Umbraco.Web.UI/umbraco/images/collapse.png deleted file mode 100644 index 9cb8909df2715dfafb15ee92d8d3d2eb93c5aa51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 388 zcmV-~0ek+5P)p00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz7D+@wRCwBy zlCf^WFc5~5KnP+e8!Sw8X;rE13_KFg(#Po5H(;b4SU{D!AThM6S}8?Hj)QZyW7BhN z1%`;Gr<3)c{hjSQ+kc#`US2m|4IABFdeeueLp?*XS!Y*7t!NfOex6pj)Y^A624_$y zRR*b2h6!wZ>WjrXY;_nkPzxb4NEoA(5J#Wda=95#?oo6vBB@$?hUi`Zx2r)V`aECR zgs9yInwCINRH@KQlq7sKzAF=YgBvX+x~md+hU3+BKaM|C4lI3VsW%*UzQ5Kv)A8pU z7vk7KECNS+Ijyuiv=N}@U^Wt2a-{KQ+!UWwc96iK)P|;HYi4&pf4ynjy*w3u(Rf}- ig<)ic|F1vzE5HEz7TL$?rb^NP0000LG#kBaSCzVZLZkDpc?`hW27 zi66iJ6;3+);QjxjH~!Dx`~U5?|M3|!YnIzZ?0|O(g%gmWGmr6)V zDl#!J9#PO&V|}vO=!7FH15=0NyxCKyPMyx2G@)aU4M^#an>UX-+;UkeVd?UO5hUN< z*w`4&bwr$9l10FQ=aUc5n>TmXDA?_9QCJMtaO1{}iXVy$8Vn%WO*wDg*f@kLFo=L% zktrr7Ho+3c(^E)DV%C1Z!o|QMzF8r>mdKI;Vst0D*AM)c^nh diff --git a/src/Umbraco.Web.UI/umbraco/images/cut.small.png b/src/Umbraco.Web.UI/umbraco/images/cut.small.png deleted file mode 100644 index 9e936845975eca508a6af6255653ece236d2b8df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl^qXECGJL`I`IRn618r!2~@SeBSwclyMk=FZ8CcB|K}UAJKAc1H6>yLKN~yJ7c% z{d)qF<}6yWeA3KK{02>PmTYS1oRgZ@VBu8~o7MRL|9}18iRXcKDU<~H1p~SG07FcS z^jo0XOP(%{Ar-fhCb%;*Fdktz^5{{*G6v}m77m384GhNm9Vc}dlo**fR1Taz!>;mE z;Sd8Oi_nDhiDFa5pC&P71C?!Hkd~fo!!Le=qq2d4iTlLq4<`#wHI$xUW)W~{@Q>>2 zwV&^@omAp+FCpn=1h*?PN;xixb;M*z@>18D}E4tWAC hPjdKf=g`2&koiC&DES@VQK0V_JYD@<);T3K0RWk}yh#86 diff --git a/src/Umbraco.Web.UI/umbraco/images/date.gif b/src/Umbraco.Web.UI/umbraco/images/date.gif deleted file mode 100644 index 8f73cb39a6cb62775bb7f9b9e625f87b79f9c6a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253 zcmVhFn1O(RrJs5>N;Y!lF>P2#6(4r^8?}FqjUU8)kgP^w+004+M90xNXfhe?c@(f% zB$WpyQ@LaUCIS}~gbErIeo!O;6dDy7h#edpb0ZuF2$2y0iX|HX8XW@zW-6wqCLsVj D1txDg diff --git a/src/Umbraco.Web.UI/umbraco/images/delete_button.png b/src/Umbraco.Web.UI/umbraco/images/delete_button.png deleted file mode 100644 index d1d6a6413568fd9f1fa4f971500d32d1bae1447c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 582 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 FV1n!}VO;hzONwV zi=~t{c@z{BkWfdk*=%TRY(!E~us`|~SDSA_Wh}+^_7}{}g`n3Lkq)aQW<7XOCYWh>{=(SXo(t)oMK_c*ccflBcw7;o#sU_Vym4tgMW3AH(wU@~JG#ZiB%PD=#nC zj*N^9odZxv>HK^Z&dyw@s8GlWj`H6^g6T>B9K98EW+- z^z>w%`vc(hdecJEjg!bq0TUBIUEMm-@*ornT?p_?fB^swfePtsItefU0000qRr diff --git a/src/Umbraco.Web.UI/umbraco/images/dialogBg.png b/src/Umbraco.Web.UI/umbraco/images/dialogBg.png deleted file mode 100644 index dc8fde7ce69f75e35d0444c0e901eaf30ccaff0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^Yzz#ncQ}}UtUlw6k3fng-O<;Pfnog#bJnhxK)z&& zYeY$Kep*R+Vo@rCV@iHfs)A>3VtQ&&YGO)d;mK4RprVPME{-7;x87V)fytbzug>fddy7tk`hj#)ktZ9vryvAwIY2!3u!| zD-KMk2zao;KtVwvKtUlP!l57{AR)lv!G{kU4m^0ULSe#+12r8}5;PS0XDpo1QQ#mU zuwli700)Bt1BHTwfPesp4;us&G#vhe0RthR_>+Z^fx(192c!_>CkD25hgk(4I#R7n zGXfS`C|Rp6WqK-dBuy!mWz$D1^~7(gI53m diff --git a/src/Umbraco.Web.UI/umbraco/images/domain_on.png b/src/Umbraco.Web.UI/umbraco/images/domain_on.png deleted file mode 100644 index a9e7204fdb8f4f8531c7f2195f57d088f96a67f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlfCD=Y zC45dH%f zJ}j8guwcaoE*GxclB$rsAq!R!iod^GZsGh@L@tl zfP#WTfP+CofWw52f`WvAfB**t4TrQdY5&2X&3`q>cLpUve!&nCqDh9Nr57rIdMA0h zIEGZ*iaCE$s98aPC4i&tfmpfkl_PJuEk6CPpDrl4`jYsncwc#iSNccx_-Gyd$;IQK zH7`2hLb<}{!%R2kzG5zF)crEMu!JclbB6V)YM~_SyRA&D`YkW}w^hA7$@FPu-)<8NX5Z#1H%h$_S)ct`IDz5hJmtlU TIJV0JUB}?*>gTe~DWM4fzlh;1 diff --git a/src/Umbraco.Web.UI/umbraco/images/expand.png b/src/Umbraco.Web.UI/umbraco/images/expand.png deleted file mode 100644 index a0728673212690d37d80f249886ab21a4e8be66d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400 zcmV;B0dM|^P)p00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzB1uF+RCwBy zlD$sDFc8P%kgA`wsH!rSp@I?76?i0`g~wpy1u7P#ek>IUNJub5Yzze^TuS4vX#Dvh@1P2x3_mZ|1}^|+V>FAv&=M3#LkIu5^7_WSd% uJ8hVok26#pJl#pvYIPl;fBl0S0R{l^iqX6rNf|Ey0000DIO(Acwl&EIXV2@^gz6kJ$v;D12HgN7CV|Np=C|Nrg(|6lo5uL$V) z(7ozJLdS!Gsk{ID|DW8w=Kuef$=&PzJl*vF|E*s?AOGF%_UBxUQ{lXTjt2$_8yXgT z*mmm0gf;87p8H%dmzn^R3ENcki0M|8KIyQ_Sc8)9n_< z>>IQX%zF3mr4;MsPakzAJJ~*cs`^P}o2UKi#oIJl;u*utj1vsrd^_HxaKraYs=#gC zKE)t^r%<-ums;Tl(>PAH8#iA3J#C)-Le*0jPoXWq@QyWyK~0>iFndOE71-CKZ8W$<+Mb6Mw<&;$T9v1@h! diff --git a/src/Umbraco.Web.UI/umbraco/images/false.png b/src/Umbraco.Web.UI/umbraco/images/false.png deleted file mode 100644 index 561460d601a5646a597149bf2c63e187a63d8e89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 894 zcmV-^1A+XBP)H{e zgve~t#T#qeTi=XX){_9Ek}qU)^9w(f1NF;3=K5+stp*&`L}qjEi(fy}*S9IYz6%tK z0|aoTg5{Yzm_Bv-`t^%{e6&aLua5zSO=EYa|J{*wTefPx5Xdc-kSi9Tm|-QGk!ni7 z&@|k;aNZmH;m6&3rGN4f;47k6GJTmdZQUD8+pS|_{6=|peByhj>RzZb{AhDy=B@tz z0ZZ4QheEh_bE0~7?A)uLGGEpKAl;ch7-|Ta*_kY|xBi^U&(3}D3G>yJnp5d?FXwV7 zdJ-N9gu{_&Tl?CB0N$oA2rq2y?z&VZon+uU_ouV3?PvZC0B#eBaC36F(9t?5WgT;= zl$&=P>Rc>`paTEe%v_JI8~V$3E>8~>!%#T25-T!{rKVY~Iq0C314Q8pV!a+(@&D>iPMhJSXcwD0heJK7NN z!~!eWbqSx)p}T-*3&O0F=vKl6fSOLjHBtlQ`KX$vf7Nk@jxT%106^+`bY3GEz7GfR zL_JV<+H?&q0RxG6=0GBRG^uzHK>$x^{*oj2e)BN=F_EUlBHI&6p-~9PfTwlG3(hGa z%FkGjaZOocpi$UxzNA Ueb)Qkw*UYD07*qoM6N<$f~nH4#Q*>R diff --git a/src/Umbraco.Web.UI/umbraco/images/find.small.png b/src/Umbraco.Web.UI/umbraco/images/find.small.png deleted file mode 100644 index c3f3f42e2565cfa2ae93591bfa3f5fc8c6791f1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlHQ?;;f3qg7 z|M>6!?~}bVPQP1z{p-dpN2V@0Sv&dYzr~in_E+tkkq^`~?SW@4kP;{f@(cct3K$+J z$4Ua_4tTmahE&{2deF|`$HvB%=cmA|&>*2ulb6Tm=2n)MS6A1-(8N(tw0V=!recGl z;*ZQM0xk`+=Sr?%TG7@huh5{t#M2|wbH?Goi39us4hD>63mCMGjhXEo7!Gb>2;H!0 zQ<1?3u%7POf;tC|9s(*#U^Me%%UdAFr?+4$10##jgQtl}51A}`xKA*0D6kpK@0={p z#=z9UcqYB^^G`;mjz-?8=YiUIYF5+$U6b^{<>_ajiOlv=3>*dw=4xE?cJ7Q30{Vr) M)78&qol`;+05ztOs`9(+SbF*YujcY zJAG}+tffmg9b33|-^q)2{bKT$?z+}7ZRM0jTelv*=9gH}vgnvkOn%F(ty5MVGxSW3 zNG<;U`~Sv+=eF-Vvi0`&DVvUMz5Kjo>cYtU+Ob@VarmW_^Pet4J|F3j$Qlw zf9d1z$6kLw_WA#@-~W&O|9|cO|L6bzfB*k~?Y_Mmc5J`?;O@`gpMU@PI%isTaba?C zuGi9e#Xy&Tc>A!r&}-|WlhHqcJSUPJ8(8nO3kAe{&0*XIb7#SGs7<51(0m>5$97`BPIYc599eL$B zgtaCdnvmGY%P#zD#f1qA+1LbBrB-OP9%}0sQZ+fk`0yYLlbDKH!i>j<8HDwmCj6Lq zf$^|3tHikp8y~qGniG`MvA{8zjYouCC@aCj=}6C9eTM=MkH*AK9>at^2@{hzLp5a= zDD1evNNA&#O4l+k?x+vp907ALaRZnVXvPH|Oa>)FQ=yet|50 z!bYefcglfiT5~=09^8leQhj9fjJ1$f>YRv{=JGcmKi5=@UUKAF^w)}MgURRQN=Y*p zsL#NfZHiwSU!hr@fBOODu`)JzWccU+vIgZut{j*k z4T{RNmq=pv{59-`?>|Dsx&~YKVCn(4EC?T25&v#;gJGSkM||uk{ZR3`^s6ss6L+qT zQh{Rpkp{;xT6f$FCklBbU$im)0I+igF5bAR{1spTWQO;P<6#c-00000NkvXXu0mjf D2G1D$ diff --git a/src/Umbraco.Web.UI/umbraco/images/folder.small.png b/src/Umbraco.Web.UI/umbraco/images/folder.small.png deleted file mode 100644 index 7b6835d04100e6ea4d488a0760b72ba71b353144..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 413 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlj%?@Rw*&i;PA<=!Tz=lg>0ZL++#&FlZ~DgS?e|Npr9|MQmr*R%J|GyMO5 z&YVh#t$h;z|NjS?=csE8a*||8kY6xR1S?=*G~wC~RJ7UC#WAGfR?-9qW(LM13W|z6 zEF6pt3`r9N#Kgs)yLT|K2sCfl!6DGm-N>LFz@X3|(Gn@q*x)I{;=lyba7Im|jYWll zsl#zbq|Y3AmIf`0pGRF7L40SP6b|c-1OY)|Rt^QWL^eY~JC1}$Y+OJiBOC;nZdBX^ zDdf;mU|>8G;~>hw$RZ}t#3UzX7znoCnQ;+N=u5YT(F6vd{*wDlECOu|4h@V9$t*ika1(h4+o_;4!#VGM-K3Yoj%Pa zl$ieL2|I_v0tRMz5d$MeRt6@H$cEm|nav>UB5pLM8E)L%^wWV6sQ*Dao8&QuhsSA`D4Afp7WG*3*Rh; z3xHji`;f{Qr-YKUcE+jR?efSretxx&WDEZ5Kp6he@Ps65>d=M!?SL2nk+yO=?eq*e z(CtEpt^NypL5|1sYZ`?lUAUkN^c5N>XnA85@Q^xwjeFYOp3C#}a zHn963=}o`!<#;@v?ns|m4?LBXgiB1FGzEnM(t|}KyE^*qfGRmEB%Pk}RCt;_Pv;q; z76HNqq0F}lnk-J0#Dba+rBbqa0A~EtaONtWn}_d_M-ZpT2;?ji3JStqMa?@ZmdPX= zTq#gV_Ido8pfaG-T2k$Jy;cl*0_LLNB}Aj*q>CgnRRXGH^eBHh);MQI_PI}xb1tX|OddyW7d8doF(D4G0y&Kg=U1)lrvBkRdEgAn&YpWK)7FF_ zO`KDR+SUr8RB?R*V+siUO(){p8Ev7H;ZSCO#!x@rpA5_#7?f@4^r^(u+m}It5>W*D z01$DV!W6U#oGVnbeNyJZnHA9JOv$#X*T91F^U_IYN~VH2gffuvM6+8egCf_g{|0&7 zthJt^rtYhj1}RrjRxZ>}H8ti;eauI>>0UIql?R%KiPs;mGXsZUZf8&wEjF3bfvvMZ84)BX)l6^Il7Go3rYTLQ8s716vRLh5+D9^vS}hjT=Z5*S35 z5hDD6A!QEqGnj%>)-N!&3h*lxRQe;yRi@?+enk_n6j7*DCSFh4__kfqo8_%Cq}0A3 zC2Oj76@t~Eogb%n2;4$a)$^vZmg^dr3uqaJj_2bU3-~2QHI{05h%BiraW5yFi4g{M zo>piBzZhYM`U?{0zQ)0*L1~;ibKYpws4-QP7cP7(%6y^brj>(CJbsn8#=A_mO+-{|m9l9vi@W f?D3D|j{pMzZ4LyvY9Yo&00000NkvXXu0mjfjf`b8 diff --git a/src/Umbraco.Web.UI/umbraco/images/gradientLine.gif b/src/Umbraco.Web.UI/umbraco/images/gradientLine.gif deleted file mode 100644 index 2b359cca13e0d215360682a3651579658e2110cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1482 zcmV;*1vUCdNk%w1VITtm0QUd@;Nala*4EnE+Su6G)6>(?(9qA%&fVPH%*@PCP*BRs z%E-vbj*gG6uCB$z#iF93!otJA!N6KtTClLNyu7@$wX}?kjB;{vq@<*Xh=@{BQhIuN zbaZsOy1KZyxVE>qR8&;5va)4mWnNxhf`Wp+zP_lasC;~UtE#JHWMrF~nx>|vmX?-q zaB!fYpq!kXczAeLR#tv~eujpIZf+9+1>Ehzz|NsB|{QU0j?%v+s_V)Jp`1t+({q^gDC-=;-M0?(XmJ@4>;rZEkJ)`uhC* z`}g+tl#`SG{r>0Y=jrL_gwy~=H~6}?dCc)qM_^S?5U}#!^6YN%gj|(Rj8+@R#jC~R8)zHiC0)y z;o;$P*0qkoBI0umzbE0jEh@aTb-Sqn3$Kx$H@Bm z`S|(x`T6?T*wFMf`k&)}{>~C*x>g(#{<>bi6$mHea&CSi`=I74M&g|^$+uPgo z^78HN?Be3%A^8LV00000EC2ui03ZVa000R80DmwN$f%$}Mm-pn>9Mef!%7f0Rf?9N zqQ#2kGHNWRp`*u+rb31UQ4560ld?d_q9sdKj2OjY%8YrdST7wqc1SPgGAvep<@BiOKDDN=|SOSbH~vuKyC1M;n{+eVGV z8JP{QLgTR4Ph=yOk^X9b}+~R`a!-x@cSWK+3V1bz`qk9fugn+UC7(bpGHEcW5r$4qsg^F6I zVxfg7pn%AR8~$>{hlw;G;)t1K zGQo)_ilJf%ERy0PCYV$Z%8WC9^2rCHyiYmHLV5E_$oRUfjOfuO_ zGZ94a#*}aLVI>3*z@nv=4mbeKmtYPtL^j#jv%r~XhM}ecY_b_kES(@ACj$83qYpj? z-Y|+E0W|6;I^Cc%z@(G@VW}npWSS{I{Gfu$0SD+I zs;FCBvc(ouq?#%@0JMtBt5?LQD(gRZ;K7F!xZ+yRJ!`Bn#jn3cl8+sBc)|u7cidsc zvdhAwh8mF2Q^mB?PU4C@`}iY?6Wn&I#kXy=;f5Q=Kw<8=?Yxsl8k{WQ?z>C$bIuuO zpn=8{_~tu~Bj$YZ1s4F%F-H>w7u>`cT_D`WHfER+#lsJ469qT!#1V%gWK2T_6C8KU kgdB2YA@UPx# diff --git a/src/Umbraco.Web.UI/umbraco/images/help.gif b/src/Umbraco.Web.UI/umbraco/images/help.gif deleted file mode 100644 index de8c2f2492237e2ca8562473103d54467ef75985..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1013 zcmd^;?N1T`0LCxNvT|;ftM#%rE1Ra3S(eooiQa5w>TEg121TXS)*H3;s+pmsl@du;T@dFo1oIA0#+tjUix?R`}BN$_WYg$$@>#`3c`Re;Ma3t z8kS{=9?PhI5>yc;+D@9RI6+EYE0hK=-s@L)n>y5l*~%2q?8Z3p+ zZ_)az-gh?0$&YffOK)+wm@w{_6-Sy@A8m?EZ;a!s6Z7=jPIhb) zfIEcBoo7_si@M|svfQdb+O@U|HNVnDE!S(Oa`KV9V)#nA{?1G5H3{}Y@mJdTO;Sg_ z`h>|KJE$9C{49dfh}KB=_EpH)&pOt|uxixu1!F%qL5we;(dsP>+p5N!6=18{03x_| zoJ9$<)x31cs$Gr3XirO9hl#ed!)Pa>Q-Dx&JEYW1XKgD4qeehZnAi7;`+7iHYja#H4{b4kIq#@a{*)jkUph zmu@_e`j+9ip`cy$T@ATg_9#o|&f_JO`)Vs96V_#x_}&&AlZGU(p5TWg3#E&rQ$)C6 zucl~0l3TXr!Rh-8mTb?B<-`|tZ`kpa2ranhQl!SbM# j;FHz4hNz(I>v5^UA&a>8s>gjb?z$&$4u{ST0(SocZ?Dih diff --git a/src/Umbraco.Web.UI/umbraco/images/htmldoc.small.png b/src/Umbraco.Web.UI/umbraco/images/htmldoc.small.png deleted file mode 100644 index 61074a4d94142e72c0a7d6a6e2adc3e4d6b7c693..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 507 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlOAb|77RS*>U>Wf`bo2LR!7sx0@_| z>XzB5=A69a+N(29|H~!JYF~LLa@wAV$j+Lo(*I!4&-SboXqQ4skY6xF1SiQ56C?c= zsP?O;i(^Q|t)vOlr%!KUVpCIKS7?xMU@8(gaDYjKkwakx!)6QR#TLx^=96t47#dlZ zI6Cyroj-rRqnJWN15?wbnwmFnE)~hFIkSgn69eNBh9}+~8#Z=!aGW+Y{`~1Q6N^Ce zo-H=rOBTpTi<>iRdoTb^Z>T9V)6+A{(@SIwTfpGJV94F#z>%;NUkm?^lqPp1r~XtiNUEj0U9`8JVP>Idk?nfowf+P(fcuQ%y~c z>q#>U15=0Nyos%?!2tmclZBNS1RNBWpH^UuY+zvGJaJlyv4Me^!S}nc^H1}-4Zr|k N@O1TaS?83{1OVk1(NO>Z diff --git a/src/Umbraco.Web.UI/umbraco/images/importDocumenttype.png b/src/Umbraco.Web.UI/umbraco/images/importDocumenttype.png deleted file mode 100644 index bd898de94b1659470a34a212f2ec3d63f153eff0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl4}QS@!?`?Su&*dX}$oD7diT!2iG7T)S7DXjt(-q2s~-|E~(B?hdGU z@aO-3-|7{AE;I&oeE5BPmO;XXgb5EEDlYsvT~n~&gHz$WfQ|=$&Nlwr;`#soorVP; z7Vm%e=XCksO|B)=Hf=lg;`g0-|Np=F^K_F_;r##q-~Ru9ub+<Xne6HXzp>*o9U1wkaIal)^X!=XV5}>dBN`m}?fn4eVhBdyw&jC%I z?djqeQgJKh{86Dp1`Mtj6B24lOj@b;aPAaDKytj5+xBd#Z)7CH9G}ipKHCbM*srsmk`}snh)4fyPy=&!ORLFM0&t8FF;oRT9 eUl`siA7ID`({-%wy1N(XOa@O^KbLh*2~7Zk(OxP5 diff --git a/src/Umbraco.Web.UI/umbraco/images/loginBg.png b/src/Umbraco.Web.UI/umbraco/images/loginBg.png deleted file mode 100644 index 18d3f1387fa20952d01aa7f8ffa7a016454a8836..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17566 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 KBbFOFM*Uyq{^n8u diff --git a/src/Umbraco.Web.UI/umbraco/images/logout.png b/src/Umbraco.Web.UI/umbraco/images/logout.png deleted file mode 100644 index 97d15fa6b66ef5864b7400ff6a6a97794e5e289b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1011 zcmV8 z(vi>l`i@%~8y#R6Boc|$;TJFF4mCAJMn*=I_V#uxEG!_EN`bEyU?^#KcNZNU9Zg-` z-PgXna-~lMT7+_$MX7|c!=ZfY_eb7Gqrrb-u?Ljzs~?legg_v0;{1gR-`CaD z__etl#B3Jq_BP5Ca-jfO5U6Aew$0{09}W(F{qwVDZ}a&)xN3p6wl?R$jT;Y*9OpNZ zEFA^2xe0}eFqsT^x(lnTsD#51H5$9;>{;*B)YSO;`g)o(8jb9CckX;69XuE?CKAx| zJoLwop+vy_l@)-BaMUBNs|##Aj`H|81c$>eb#@lV$HsnRJs!{DCa3dr4Y6>Th4oB zvYMVwAKAu6q^R3GfgJR&nVkh&Z5lbo6Jed%?BE%eWgyGgqPOk5dCZhIsBA(&V==KaY>@-9yXWyRa-TquAMr z;>C+uq>va^9Np+0L9hD%j(_xL~V~&^?e7@1lTKjm~NFg3)N|cl{7?h;d`e7-R3jP`lJ}Ap_ zG)_~wN%Np?qe24pdOfB?p%wGc(6@u}_zyk3y~j-r4Gy_fVp7Ru>}@bOCs{0uA0iQ@ hwY62nRL{Qx3;P7$n diff --git a/src/Umbraco.Web.UI/umbraco/images/logout_small.gif b/src/Umbraco.Web.UI/umbraco/images/logout_small.gif deleted file mode 100644 index 67182329f2aa090b27d38fff6b64d446647fca8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 996 zcmZ?wbhEHbL-H?mbQOc$)2VH%R|gkksQihi9p7AJ@$J_2mAqyVu{% zX!>^g;ID_be?7Q$KS1M|uhh%x#5a>`-b}84oZz&_ko(Kw?LV%cf12X-e#yjF?fFlV z9AD)6e7|t~d4ABNFq7wb0ec}&Gft%YP>;%{dqy~&$D}8HfFqP%-U=t_i4lY z-A24m{1jdU$~^Q}+-D>HIM(*()ziCdq;7kuJP6Z$7^3ww!R~E+)SW=RtIqO!eDt43 z={Is7kmOsP{P9 z^4EjgH=Si)6o$R&EqmlGeK$z^d79(1+~7MA7LTo2o@IHxYR=kZCH;0<)8k0<+eQo@ z7Iwd^P5FNQ$jfZ!2VTlAl1$!|`hHk4;ju0Ii}IM~MWK7m1fQn3zMIvt%T@DLQ|60I zhdrL!-%cNRS)cav+}#1<79T_1rPOiL#)u*RNLP0CCb1`kHP%6~jFF0r5N zb?AHbpwlJcte{nsh-^d1!bvlQy!J?VIl1^ul;@XWNCk7RCwAH zk;`ioK@i5Pt7m&=vYW+MVn~!AisZ5wLB!xeC4zX+KoInx2>t_l3rKDvo{S#VlOU)l zl7kRL5D|okips)@9z5v92g&YcUfn(2(`E0asfMqrzWRQQh`RT#QLv=t#0Dv4Af^)vd|5+(t9N85(Q&xUh+IMLZ@0$@* z`!R}1E7g9Nt~~Sw;{gxjz-1{|dR{5#2FC6sZ4);PjbVL89sZ(e0mMZ8Z!z>NpLoQwOn=o zZM}R$>}h4ocWOKLZeF|(2=d7i1MvB?pYtsSjD<0VVg2}gG(CgS5b4~s-xg1opuetl z^}PsLXLd3G#~V$L!QbSg;@-H<2J1WxV|1=NUg!BfKfHr^{~{bP8}K#USw@zOW!6Z8 er`rAh0t^6_ZU$>R@+KYt0000{jOkN^7l@c&GZh~iHcMg|6U1|5(JkXi<2 z3yocOtPH$erWgmmQTGg8WR@(l+@wwYCU-J%bIhAarZaPJHFj-Z8*mwuMNsU O4jESS{d-Lq8LR;h)iKuq diff --git a/src/Umbraco.Web.UI/umbraco/images/new.gif b/src/Umbraco.Web.UI/umbraco/images/new.gif deleted file mode 100644 index ef0178cfa48c056e4659c2f586823b18fb2d67ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 246 zcmVR=uD}2o(&5bkx2hNb zSVFP=fsJhyN&Fy3rVgMK&~l$Dl}A{?5VoSHYEperE&JExp_QUCw| diff --git a/src/Umbraco.Web.UI/umbraco/images/new.png b/src/Umbraco.Web.UI/umbraco/images/new.png deleted file mode 100644 index 0ee2a7d79efc3389e202d771b7327ec9728fee77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1035 zcmV+m1oZofP)u{|D~j-MrsUi$x{_3Efwf= zyI>TUws$Zzc6mcZDyq~ex-52> zO-3B>DXiDGuu**pugg7n`Fz;DyuAEpT@@wCGUfC5u8fDnpJs$2I8sHvQ3PZK6R%ys ztY;c_)`F(e#DOM>uaCY3-z#0a`|;g7CcTNKydF>Q?A+X)vT&@+;~2R*hwMonT$4jv zY(wB%NHt#}-b|n(SFnkf`0mw13|t(%?Q}Z(sGHYsyiOPr=ENF!r3F1<(UcoV)iNlS zO5pMwxLOuXu?nr+1|bqwTdUaP_k+_@(<4N+#;p+83+*y`%w3prjUiXgqjX$?P;H`m zQpOzK#yjRon000>X`kZ0^bL~YA?fYubrV7pT2w^puk1QH`A$qaFTzPXK&u9Gg#u0+ z4RkY|Ft7$#bvDGQE$Es+yTL<}jf8MHE^TsJ>(R`HeLs1CSUCoY5z))qu%MZRRej*H zehDe+0JOp)A;n>*Ot7>%luD(NrG9wyXq%SFme<;cdFQckMADtZ^s{g7`zI*sZT}g6pVp|iPghqu)%I{ zpo19UpiMBc6fBYv6T%eg$&(+`*>qgnYVp$E-d^g)yB~!7{lUO$x_=U(a{k^QN%w23;>FQyHGcgO|1X`002ovPDHLk FV1gtO`K-riQ}i58amQabLt!W>+0M0A2@vJ>Wv2vA3S^h z^xgY6PoF)0_x|;VkFWm!>-zt{AE@i&#}}VIz54X&^~aB|K74rj?%nf`A78(J|MLC& zSI?e3eERg^qeu51KYsB4PtpJ1<^TV7|M|J@|F?|)KPLSDnEU{C{El_nG7W z*S^2scD{PF;{RQ#{|`0(KM(l&wCDZXhyQPI|G&cX|CZAKTblpx8vMU!`u~yppNGl+ zpXL62mizx4%iH~|-%oP?zr^$ZGT;A8f?v)^|G#Da{yVC~cdpu%SM!k?aB6%1trJVG9AESB>Ymjrm(FU5KCz*#sWkS`#>qF1ExmSh>Gcz9=1y+d zyLI`q`8+Vtm-eQ{1e`s%F*d~W;GXTv7R&?22quL7?KXzSTB-Tln|)UWo|j? zOX36rKWVd)J3l@KJ!Y4%u6Q@+XA{f08A_^dM=o4^%+@VzQ{b^u`8X51kypqP1|^m@ z4gu#)98M1$7c@J?&S2S?sMI8A$R<+YV)25jS5&|$L~(-S!xl4_7@17RL&xXva{aWK zY1k^DprgNnBOoKKa~cz;70azlF8-}8nTKRD6h0iBXId|(ykvs|^C2#IgAy580S0RT DzvE=g diff --git a/src/Umbraco.Web.UI/umbraco/images/notepad.png b/src/Umbraco.Web.UI/umbraco/images/notepad.png deleted file mode 100644 index f8b8ccacc677ff71b71df8ebc40ca398fd04544b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 489 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlD;r~=Wo|ux-s?M+X=Tng(uatoqOmt^Wp#h|K(RMivrrEP!i-94CLYi z3^6g%Z-Htbd%8G=RNP9M@cj8BR_8jsN&yBA1-44Qz(Ro(vFEJZ3@icx4ULVB{$5^D zJ%Iu$3<3@gjGA_uhK7b}iGFnx7#tW3PZ)Q5c6%BDg}AyKSQwZ%R1O?b(a||`;=~Eg zOUxh*b_V?u1N%6auq!k)FgYbKhJ{)BO^|C~VB*?P$W*yzjgF3uje{)%Ba6_4)(?>` z6%AD&QxsSpZEOhHaQZYei-1Q%Z~N4#!9kPeil=BeFf_8ZuCKxr0_#rzl{VXS=xXq@GcX=e=x1YK;$aAxAr$sA;m;zVcNsig{an^LB{Ts52dm1} diff --git a/src/Umbraco.Web.UI/umbraco/images/notify.gif b/src/Umbraco.Web.UI/umbraco/images/notify.gif deleted file mode 100644 index 55671b5b4765e03aff2893c6ad2e3461d4523b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1003 zcmZ?wbhEHb6krfw_&$Z9E#K|z%J_3D-_(?`v1?#myS;V_j%IY12O-fH2?oT`~U9^zdmm~vY~6s z=I>LdeEI(4_vKU5E}fj^A9<{!z;k9t+U{LH9&HUjwr}dGQy*d@SNHY4IlVIS)w#_7 z|Nq^;vEu8OKZ_SXc>d<=_oqwm?1=pNvM)CN$kQ{apB}Xr#5ix?_CFzJ{jI~L|Noyg zwDtV|XKq=%`O*!K+w$C2t^7Q7%GcJG$N&HD{d~Xm>XolsxBTdAe}3)SkNNZ7|Npb# z)RD;-HzhuQ^YOx3-?O_r|G#dzxgmJ_!fLnB#EYv!UtB1hKKt&AQyCX8eED|2Xi|;a z{`D=bEiZom{&!(lMpez@heuLx?n*zq#N|I2FpL7qLO}5+3nK%=6b2oTF`zubz;S{> zlT*fH!-9j&tjk{cz-W6JZz@?)ZdBU3@bf*Z{T4o+4Kt@?9Q(Ia7=v5%Z3!>t_xSgLUIgw9O-~{85xu*74dKNgTx5*p1@o}VxBpp%^G~rTRrQqp3 z(IAkeYNulJK_wZNmH-YWmH-6~wy6c4k30Gs7$P~g8oqGs;&&Cg$8h7r0iHwG85tR@ E0h|Vd&;S4c diff --git a/src/Umbraco.Web.UI/umbraco/images/notifyOld.gif b/src/Umbraco.Web.UI/umbraco/images/notifyOld.gif deleted file mode 100644 index 55671b5b4765e03aff2893c6ad2e3461d4523b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1003 zcmZ?wbhEHb6krfw_&$Z9E#K|z%J_3D-_(?`v1?#myS;V_j%IY12O-fH2?oT`~U9^zdmm~vY~6s z=I>LdeEI(4_vKU5E}fj^A9<{!z;k9t+U{LH9&HUjwr}dGQy*d@SNHY4IlVIS)w#_7 z|Nq^;vEu8OKZ_SXc>d<=_oqwm?1=pNvM)CN$kQ{apB}Xr#5ix?_CFzJ{jI~L|Noyg zwDtV|XKq=%`O*!K+w$C2t^7Q7%GcJG$N&HD{d~Xm>XolsxBTdAe}3)SkNNZ7|Npb# z)RD;-HzhuQ^YOx3-?O_r|G#dzxgmJ_!fLnB#EYv!UtB1hKKt&AQyCX8eED|2Xi|;a z{`D=bEiZom{&!(lMpez@heuLx?n*zq#N|I2FpL7qLO}5+3nK%=6b2oTF`zubz;S{> zlT*fH!-9j&tjk{cz-W6JZz@?)ZdBU3@bf*Z{T4o+4Kt@?9Q(Ia7=v5%Z3!>t_xSgLUIgw9O-~{85xu*74dKNgTx5*p1@o}VxBpp%^G~rTRrQqp3 z(IAkeYNulJK_wZNmH-YWmH-6~wy6c4k30Gs7$P~g8oqGs;&&Cg$8h7r0iHwG85tR@ E0h|Vd&;S4c diff --git a/src/Umbraco.Web.UI/umbraco/images/openfoldericon.png b/src/Umbraco.Web.UI/umbraco/images/openfoldericon.png deleted file mode 100644 index 15fcd567111e13cfd9e6f1c0cd8103ca0e2d76ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFKheKdVb6i#4OEw{M26cw_Gj{yv63W{1!+$*s*N3f2 zemsk&HQ&u~&f!%4#A(caCa+!4-!jYT%bH^M9p4*PZTkKrpyj95hk2~iCLLVQKL3cY Ud_U9dR-h#ep00i_>zopr03|O{b^rhX diff --git a/src/Umbraco.Web.UI/umbraco/images/options.small.png b/src/Umbraco.Web.UI/umbraco/images/options.small.png deleted file mode 100644 index 01665deda78b4df9e17fcd2ea6fb6bc0609cbe5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 396 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLls=*-O;K0bI=Vs=nwty*Y0#^eA6DLFJ%>y+Mra{w_nGH1)7#tWFSt}gP zj0_omDlmf7G{i7WV&`UH5r}ALWYpmo13Ga6gU|zz>1=f-3<3!ZI*pu@yCpMJfj(mJ MboFyt=akR{06sR1-v9sr diff --git a/src/Umbraco.Web.UI/umbraco/images/package2.png b/src/Umbraco.Web.UI/umbraco/images/package2.png deleted file mode 100644 index 3ee17b4e3b6a86640de1f96c3befa4bae28cceda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 966 zcmV;%13CPOP)M_2H~;pm^mP1n6P(azj9CAnNxD zap&-smGy2r>o45UgWh3qTz^?|;~R?kl?gLq#(5gf5(Hg9h7&93cW<&u=XRJ1`9ygBX6=)mw>!1Vm5{jLy!cn6c_aKnG8sI z4#FY|AOIwZNrIT9JZMWjJBL0()=K9+uOh-o88i?Oi6nw-5$cS=nGaC_?T~SaJ`gm) zX0C%5FF_BsVC1O1jI%Hdr-fTy-28X*_p8FP*Ynk7Sfx2?9fvvtprK+(1Ki{=&?3sz zfJPmA4{B(g#BgOS*!?d)BgRtnI-TpILFe7s3-fPPRx0^oh7+p<4CesDr)|`?n|Qo^ zjH98ejdHbKynJP|RG#^i{rveSFez}XIhvgw;wYNRB%E7r)O|1S+t>Qt1{y~yR#QuV z<*I8x<>zL96bAo=yd1fXL(BC4R%tSi78!QNYfrzp@l`7AecjmJdR2O>E7hCJ_f6Z} o(s3jW8ucIuAjvmPdH)G802_XA+b!7j+yDRo07*qoM6N<$g0)_?82|tP diff --git a/src/Umbraco.Web.UI/umbraco/images/paste.small.png b/src/Umbraco.Web.UI/umbraco/images/paste.small.png deleted file mode 100644 index 3169d26d0fc21fc20e3bc5f8463879b83b4919bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 632 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl3sR=_y4~~CLa60w~_JPy~bIm|L0A<`0LlN|JM`#p9(8)ocjND z;s5`g|38)g|KE7@)aCyVGyi|9`VRuj(rY%h9{4}?9OnzyXI zF|u&-xvZvLm#-g*O_}`vOV9Ur`~QD#=%4ie|Bv-4g-d2l%DZ%Y!@1>V@4j#P{%&!7 zUEbPesiPAF|Np-7|J&S>nO75X=1p3B?Ay0*ap^OFHvQVS7U*#Xhms(_V6fHX6AXu{ zH@X1L`sC^27*cU7X#+bWUt(gSsKIGDg$9W?Z+k^TrmCo@_?$g6vyq8`@kqk?Wor&H zva#K=abRp_}J9QDel3b(9pmXV`OwJ?2p0$4iN?pg#``sR;*YtYfD3euKMEM zy|xYvjI26Gj~=}#x^$_irKF_fTSg`hjRmTzsz=W;F!}82`NqJ=B62}cP%tq4!Q;o1 zpQj~lVFwvAW66>wI?OJdi-ATovaqnSrb<59xpQY^1JFGzLIOfUm)ch(q=MA4RXfYE zva)9HU;tY7_t2Tchm+Yu&MT?2gWSW`A#W^nX|6uRFBj+z(yL{!^snciw{QdXs`;R3{m;V2^ z_}{N-|9|$rdN%*#yEFg4G=6^B{QrIBlk27bU+4dOU3TG6$%m&ajvt);?@9Xi`j&Fn-}Jtou|5{!|=iW+y_^8uBqbPvU>W>i~FZe?E3Tj*OkkcKD>X=z`*eD z-@m_q|9<}b`NfMD>({UU|NlQwa1@M&0NEj+_>+Z^fx(hN2V?>$PcU$tV<_f0Q@G$j zuQV$YPmD$YJ0qiQ1%t+g=7$a}Y&<*?0S(7_WyFfV7<_1KYvwm+QMr&9c%YqMOlQK6 zfCX*5ZB}VNJ|rG&WR_>sTd?Eig{CekT?LJd2?+;UIaI`|J`^}Px3CLkU0Gq!*c!sh zt&wnJ!$X&L_SmI*0SC$$nb`!7RLNR2F|vrNU(*thPFm+6$nUaa!oq{PZ2}5^o_QE5 z9!@yG&o@0|qd*&{K)+_djeti-+gq)Z<`g_+Vu=*cuaVgx(ZJR%s_nPs!@-~g2L@{Z DsHx&G diff --git a/src/Umbraco.Web.UI/umbraco/images/permission.png b/src/Umbraco.Web.UI/umbraco/images/permission.png deleted file mode 100644 index e8b99447e68a3165a8e6c0f635cd360e9df9d893..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 581 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl*Jb~|H2!-S z|L;lqm2Dws=c(@Awe8=pX+JLe{{PuKbz;|-TLrJq#;mF0{r|r5|LgpZ@6PfMza1Lia!@{Akfq}ifql2eihQEVXz#)M#Fi=oYF)%WsXH&zu<= z5p?E;ghE3Dlhv&l2SznDF;C6~(hN)-Zw$=J%Jjs<^aSn$r4{M|6Q)c`5@t4LWa44a WDOJdM&@Tf@9t@tYelF{r5}E+PVGj%d diff --git a/src/Umbraco.Web.UI/umbraco/images/protect.gif b/src/Umbraco.Web.UI/umbraco/images/protect.gif deleted file mode 100644 index 51323c0869b968fdb5d0f482a80a80cb5295e67b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1023 zcmZ?wbhEHb6krfw_|CvkVDGrFvU~;9v!r3u zg0&l$Zd^EZ+5bPk{{Q{+|Hs$=?_PYfW%y#l@ZOr?xh2CRbA|`z40p{K?wB%MGiA7J z!f?@;;k+@!Ib(*?Mhu4ynGP8+{dZ=)V#)T!ne(m<=YJ2LUv50_oOvEPaR2w@zi7+* z-&^3nkKheQ{=Js`{{zIndyAd06aF70{lG)ws*CvlD3$-ws{dm(enhH2jnw|1YIxK~ z?R$#pnIN72*;fB^?Ovr?|1WU-S>*P=!uNl5;GHbD|Fywy$^!m3g#T}fd{Ys8uh9QZ zUG%l0p#Po8|GQHDcc=dE&;CEP=>PQ6D>aG#XI6YYwDJGxUH{MR`*~{jzY7O{Up)N( z#+m6J*ES>W<2FLe%*2&bh_y5Ji|F56^|N3cpgzK@ItWyoSE%F?Tf*fuvoppP~+{=q+ z{{Qyn-qy8ux2(Cic6q6>R=JtZ)R>SH(Kti4#KI}85prO|!^7=1 ztSm|e%8#2`xg||X3UrnUHaGTN+VkMT!RE*ONj$0=8V=_rjTD@EP9FAPJi^K-!s*5G Oz=65r05=;8gEau!MM^^e diff --git a/src/Umbraco.Web.UI/umbraco/images/protect.png b/src/Umbraco.Web.UI/umbraco/images/protect.png deleted file mode 100644 index a71c88b709a659dd2733a93ca914051d5b2c2347..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 628 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl$6WS6iq}Ic z=BsVFcek(qKRJJEtlPDo(tl|tZ)>9(+@1fwxPL&E;Z3p6|2VC?5!!zn!Y@@PHmV5U z+O(>ntmMS>-dC;ypG_Ek7&H8BhZ(=H2sjaSW-rl~j<{!kjL^z@fmF?#5=2ko-7-jgx^zAf}CButbIzS(U}O=0#_!8m6a5*aVTtH5aymd z%TRju! z&~{N(RZOLlp|~UBFH2EKk{baj0QoH-`6N=F<7HaL=VVais zs1I0K97pGuGccUf;Oa|A^}k0fzVFD?>9Yf5Tl$xS?uH3BBS85?W218&`NBNX&seaV z94g&aIE)rjeQPN3I^@VZSeCQUb_Ccj)6_uG9Evi7Opb#}8!(%Lu$nF?#IfR9gj4&D zj$RQBUDw(KJRV`8LWR-ixdS#ihFVTUR*GS%;}vL)hS92GEH($XCSd>3!JQlk9`6f@ zPxx0ygE-l|iB_hLPQi&KGln6i+UBKqPrm74<`7S}3B`9Cp zLODWEju4a|5R_H&AEp<1=XD?p8{rLIIuV247JGoX~`O$yu zkGpqm*O^5!Q#B*g<{f$X|KW%Kd6O?j#r7~ZGX4hxi?ge}fOaXA1o;I+L~xP}F)`9_ zfoeZ^x;TbZ+)A48d~zEjXQcuIhXPxrou#FtBO_y7T^Xm_0tN>LM%IdoCk+iVXU00s z3XhXhXlP(+vPsw{vb%*PK-hu7@PskfsgoN|oa#F1wP_8w>&%Ko+c7 zqabY2Dk&k+z{J4FB9f3aX{xBuW058f1{Q(lJuDs!97iNwTwI<$Wdzzbk6T0Gn9?~V zCPA?KCCkb@6Bjh{2rxDFk4u ztCzFlqpGjt@&9OThvoDCtj6i8sjbM~|HR(^MS9`E-2b`H?QwgfveW<3=l*Y+>d@x= zw#@Rp)Arcx|9yqAw9D&Fa=nA3=BTT&+S%E++W)lH|CGGgu&}UhrPq(C+@rtJvc%Pg zkh7@H{l(z^q{Q-|syo803m72QL=k=GH zyQ077R)*oGsjX|H*kzC6Re{^E(fz5!=&Z1|#O433uCLSP|DB=0o~5ifh{!F0!`Ru} zma*lKmbb0G%Biig+}`0xa>$vTypx%^P?65v^8eB6|D&d;prxyup{s?9t%sYfvB~s# zf~LCJ|Mvg?g`v=NpYe%}vEALZL2hLlI1n;zGhWkz z40aSiWOg7o01DrDF?KLNMs_M`C2Z?VFLwGiL>2`G>$qgm_Xf&>4Ftlh>!L(L1u-Uy axWI5>-Hs(zeDJtJCyEXS>mV5l1OPi<7))gV diff --git a/src/Umbraco.Web.UI/umbraco/images/rollback.png b/src/Umbraco.Web.UI/umbraco/images/rollback.png deleted file mode 100644 index 0f76f58a604db8e10e97aea36561ebfe01d58a3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 626 zcmV-&0*(ENP)uS`bQGzxa!HZ8d+f)x$&a>7SAP8gD zRBC<>U~VYZnekHFrRVQ!TH~V|xjr)hdPzrSGy(EAiA*B87`I+-RxN7XTU`=DghrbX ze1M}SAVNojVj>iMyjE!WBEDaG^Xm0V?pY#n_Ov8`K+{eYW=&uVl}fYHP*#{Si_vD- znx5h8jxumTu`tvReb>XcTh75j)dct2^5v_m78+c?A$q+U0f^Kgc=wfs+S=XU zc$h!_<8OX;uHQO`s?&GKh#Ywkk~8n_Ikn$@EE4k-}hasBw6{gA62u27`kq zcWqQuF#_wzWt5QBi5hA;sLBF=l_(UnUv_QHSMBWJ5f8}}nZf(rQP2Y?(pmO!ca zU<7!5r|7LMN0P}|Ug(ZX7Z^9K0`QvPwxEsB)*_Q%2ox3%!7`!M){`C(v0koy`S39a z0_+KPHT!`y{gZOkii`o-Y_`|6hWuwRPp1a5xAQ_2Y}+=B&|d)t0CJ)U)b~)=+5i9m M07*qoM6N<$f)>gckN^Mx diff --git a/src/Umbraco.Web.UI/umbraco/images/save.png b/src/Umbraco.Web.UI/umbraco/images/save.png deleted file mode 100644 index 169699cc8fd529d0a2724fa9496c50722d11ae45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 549 zcmV+=0^0qFP)&^DpB>u_D=?a_}8qW|$eC_j#UoCUDL%ON7iA(VxZ37t-~5uzuO2 zh7N1S7~^MaG1l?%Q^MQV`z%I=$4@qcDS#jdIXKX)uEs=BFjeVmYsk*-78lN)Hn2I( z!1^ju3Y1*%0CR^IN@=ie%zl!xcJV65Xje;^&=o>3c8$YBp$vmu`8`9wD3M;44NSF( zlWt^L&eGD~>TWUuOXw99M$^j%C{tE5C<@KS#tIJ~?IDa}Amr~XeM*VUEN2^a%Dkus z7>z8==5gZMiZ?R_tq6u0H4)O#^Jov^g?bs{CpP`7LFhD*I|88HZap0iyEkj1N4#)_omT~0&)#?|1ZiiUMYs2> noz9H@>0|KUKhw+rKLr>7DZuk9I~en^00000NkvXXu0mjfZHV`) diff --git a/src/Umbraco.Web.UI/umbraco/images/sendToTranslate.png b/src/Umbraco.Web.UI/umbraco/images/sendToTranslate.png deleted file mode 100644 index 77a0daf79ca5fdeb51311f8a965ae14305b53f41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1047 zcmd5){ZCtE7`@wyPBL^e#5jXVMW>r?q=q>^v%MW3wu;>yAQTMdEw+@ojoy}atsm37 zriHdTxU2(jX&kT8HfslXk;p~@&D}cKoy_uv2tm-~Rhhvb(3>rq`GeN$f8mpy=j7x` zo}8SVAZ>WLApfC!ilPeiy7Fp;xBZ9Bo0Pp}?4qQQ%Bp=$OHp4)&*p`GRrGdeh0$4K zYjL_w4hy9<+nOzE{c)4kQf)Dr-?3k}yhKqOZqZe>6|yXoH5qb*VLACuj)pAsaH5FO zc|pWP0oJKSS|dES6*80XBZ@Oo*UO7I_qixhywzR2$!5#05p?IFFb$v2woz=a~IK?kmK3?^>*JtqOJeu zFMrRBWR?fKU9LgS(9-3M_aOmm8yq+LK6d%zgk{9Jn}Aom!qLm~@)8~xTVN3LO7J}_ zW60y?_A|7H(I9B|4Fe-XLq@|PjB%)cLZCstE1>4ViPQ0`->y44Q^BjoMIc%u7)JvuvK69rY#%k=Z4P1d;6< z$#JXYbN)){iD_zR?^DZJ{WKm8;0`#1$T8I+AB&8GJ_$hA)T`A8>K!AKxtzxUq-z|ISnZ@-wYo|DH0gv-xdR>Ek7(pENFPtmyw`Ho15RrvBV=|Gw}~C92Twr-w$PgY(BO zKQpL{tf*p7?d`c<#+7+bP0uBqB}+wr{MPk>!9F!P{ppSk!#~26zb)I1Pv*V*z-qm4 zDB62(p}jR=g3RhRKD%;d`tjI(g^?RlNv7eNlzBsZ`t#1*8LOEZ~bsjQ)<`Osc(i%5$g7?&1&d}iE=`w`if0V>PVyvE^wF6nqJdENgmHSY1wgPnS0Z_xPzu}Ur*?O4Od5pgJ z`u!~2oBp)Cm0}C%XHWM&39(q3^0V5k{1_K5$ i6D>*s2`5$t5eHcg2UXM$Swh$v5h~q778xnyK>#}m0-lZl diff --git a/src/Umbraco.Web.UI/umbraco/images/sort.small.png b/src/Umbraco.Web.UI/umbraco/images/sort.small.png deleted file mode 100644 index 8845651fb340417bed0367767d131d8032073abd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlPD z`O}}P2Q1Z#SX(&jg}VPD->S>5c?XjEp0zA{RX*p1UB+&!Zai|_0F1d~y8Q RCZKB>JYD@<);T3K0RRfUmskJ* diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/error.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/error.gif deleted file mode 100644 index 314b1718c2b4065e53f24455387413b58bac31a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 582 zcmZ?wbhEHb6krfwc*ejG8R_}_Im7?|On?7Ah>rIB|DXBA3x=;>FEB6w1z8_IKJ))S z=l}oQ@82`<^K*Xv%JB9rLqr5`T^;M1HChb~4Bx(Sty`!1@FD-xr+i0`3dY67MnnW8 zB*g6B@3ecj)alc^dwOjC|L3WxslR@m|HB97A3qpw+~{|AXIQmr>FLvg-@Y+ixUg^K z%Cet7c^euUPo3Iz>(-qA|M|1CS$FK15)ckY}pGLrxI z@8Qv-HkFkO<>e+vj!5p`Z+HB-_@__TfBd-f=g*xNFZd20E=Wr9sjABQ@k8j|y_M$PgaxQB~Ct7RD15<@WzSFjyV}1BY5b@h1z|C>;<9iW3I*#SLjq z%`L5MX$DTlHtj9!eyoo2%)L$STB#CJTvNj>cvN&1VqALFQpNauqEhsGZBy+y0+@u8 zoZGykQ$;w`Q@s+kTjR}AQ#BKeSeZg&r!)tt9yg2#F;7*Dvub9SJf6zw;Kp=ZPUC_v z{{vC$WX|K_GL}vL4^jgKg`TEz+nY97nHVIfHZXETGHJ*(y0k1%(wPKI5Dg5BT?}SB MW(2JaRA8_M004#L2ZZ>EP)@aT_#nLAhKKmY!H|7FKqj`vrnUV~thA@$l7Np7mK6Nd zWw!nz3nDy81UQFhJ44;+)U#dp?tP+p_f9p^-VP}iB`F5aFi@MHL-E})J~evjPq{sv zC;X85vSZfad{{Z@VL=4%8;Ftt-}9nrSv=PF=)uEX2OrVoY#T``bjmk&U_$`Jz!4wr zQW3Lnz2%>Ob@Ps37Dq;blRAY@vVICk-!qwe@}7f_4=qhiBOVRG zS}Z`3RSZ0I;J)cUp84tDO#5(S5QRWiOeXByes}kC;ZOvV(=(7FVJy0P()V4IlwU)4QctRB4mS#dqQ&($dP+v1~qg+NJ)d>Ag0o6nTZHfe* z%fnD*L|dEo3zqr&RSw?D?gShW+-x`m6ri;3+x+WnmOYIQYHo=j_HUfvZNFb?rE~<40ImbKuPs{nm8#8lX zJ>xmms-_X}JS=-QR!c>A6s@a;@E3vMRBrZ1f33UUk8(C#@I90$Wy@sTqnacnsZ*#j zmM9W_@^rXF_0Y*k$@lX?G1oZz?c`GF+cQ)sm(pZWE5hS&;}ML8RGcd;{oLaCZXg?D zY_TrMFeM2F>8X)UnXx9x=FIn~uomrE$3crjp}&VaJfW~KdG3z8S=T{`QCCsP#INSrwZG3kr0HQ0ab~a3@=3) zp0-GNCGd(oERc*qosuTGE&(5F5ZLQaDMH$k#H^w!FbL@iX(tS$;`3#R-tXv+tKS_` z<0+#?T;yYSZ|~z3#C>^l0r1;qDW_382hLj3ca$dD6uR7 zt>Ug?Q-qk6YEbt4+k#E`E*i#_D23NaTcsTL?ccNIi=TM-P}UO=A;l?CLZioU(X@~J z%kjR_;KFXANTS41o5*OZ;~eN{$?QpzgBc>iW@?H%mOVj<8JG4|D*8K##OX!X>E9j; zVM2GY`1UY7Vsvw?V}Okhs2>6vwePmNk0jN<@k7&2(Y6S+bHtoijGdA zjb%#Elxe*=WEeSVmWoRR_Tu$o>A7v~+3sG#LTSOKLE)phtsUKc58@xgum9%jX5q+( zsQ%Z#WI|I))sEWTS+*-2f-yOT&9b;ogl9{7rQrF$3Ytf43$dK#Z9wb5qFDK{OV@Yt?fgvFS}8>zr!bZXnZdLTE(gn|Cn2-5k+Ek9rjPbU!aI7RkwlV;DbiV$+Htm` z8)HW8Ceh2;MaGVk*W>f}?{y&!rSgbL{`Q|y!yTb%{ zk`<|Qmuo;S%GhL=?=P-9NBeo&9qZp6AFTffFaSr;e9@{93g`d;002ovPDHLkV1nrR BnMnWu diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/info.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/info.png deleted file mode 100644 index e5734da6ed44bfe2c6efbf91adfdd6f65d36848f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1542 zcmV+h2Ko7kP)Za;TF0teSz?ysoa(zt5b+U3jZ>Z_|u9X_8-_`v9B z29Zd@xO}OjqxGjN=Z0gEBf9lXz;cwSX<$xECAC0?z%D(K?HOY z=;}%1?AhNEKb|3dxi?9t4q4pw=)@TvUR_HLG{JxsB(t*4H;}g2IZ* z&7r2o@>pk#HX7gs9z35nMxyB!uC$w|EcM$2ZILW)-5#`Kw}HpaG>LGCM@9YIqHxu$ zSIPAq&V}_-!@9Me@QC>3+6Xdg&W?U6IJ2B# z!0q=T`qz!F&I{+8L{V_pI0I!3x4NRpCWjzN(me#sSfw4*I={B2>!zMBfpEUTJ$ck5zoT0O@Gz1A^=ExUe3 z&a~`9o`^n|v&|&Z!e9k2`Mq;Qu|N(esRTxQ+rUdc7^aU1Oft%X9X6Sw4hM< zplBAAWpd&)%|s@v*rg(Pm@IN4WSwOwNf?=aYFQPQC_*%HM!(?n*`VrZKlHH(^bAcB zPi-lY_K38qRHn?z2?7R|=;?;0FrCw385xoinoNC^*ppxqz+*8h)g4jO)ONusr(OPP zS4%yZD~>coa8X<(a25u0Lwau~ zuj%SdL(N<-3^mc%$%N@5CprIYuipI1iBlAe+E;MXJ5Syw{OFl?!#lH{%7$)T&9?Ju z|KVJsyW@0m%|bZ+vzUC@PPQ=+-ZM@-g@8>o1LfuXdtuz4&X98!VMgszj1BaCo4s== zZO?wPxA#jr#^}riPv=X-Ir5uWL?VmSK^-Mb!n5uYN%chMB)DBx2WHePgF`MzjP%Bj z;I6nRU7rj_uU>8`Yp6%boRxV3NK^(#e$c2Snr==Iu$Hv9vPow|4BQf_G<7Ijvf!#j_BV~IAL-LxnMG?pZw@V5vD+&Z&-PG+1vJ@y zS*A>6It!jASiwjtjS(8O5xP5*TJmG`l$zte9Ws z^LYY3Hw2P~#F&bBawHq+iHGB{?$blD*jL66Z^!@Fe-#rBYYvE<*F9U3N5e28hc suBd%_I^AuhGF{g9Z|T$R|3`oU0GMi>$mrPZ4*&oF07*qoM6N<$f`J9>1^@s6 diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/save.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/save.gif deleted file mode 100644 index 89d75c110e21c4b20ad72e60ca5832c4a149a60d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 562 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 diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadow.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadow.png deleted file mode 100644 index 40c875982fb5b8047f57abaef758244f714a2ca1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmeAS@N?(olHy`uVBq!ia0vp^?}0dmgAGWw={-sUQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07?{mGT^vIyZoRqFo43e8q#^L%uYde8sb+Zz->uFq2y0`IfE&Z#Hwv}f$! zlzjhKq5hhxJ=XdYA8+uvX;@&LqFAPQZ{MaAwrdx~+A?{z&tFzpp8M(B%_E|EInqVu zt;d713P&Jj{K)i(6@-s)gK&qqfIbK-Ko!CiH6bhP5Jy%BG3~(PkIPQk zo$Tj~TXrio{HA56*gd^RoAq{kJ!NY9&TBq(+S%o~Jkea!4}81WH&riK_g?Z1H^#%K z59xmWvHhyun>%f%%VwR*-C^4=W9Rw%h(>;--0=eb#M#PzhYvlmZOQ*pQf+_t`Y+p0 zjtjT7zV3IaE_@TkQFU**$h-X~twoNN&5y6O7CbKcDe|sd z?!$%;EbT9U$*imRxuxQH#_kW_@BdYJ5MtS;%X{Y5&gIUN=T=K;Ev`MY&(7{c@8P(| zbN@eUU)ika^LRds&Gs*MW9BtznOdYBobUVk;i+3SbrUQKTTd@PYAqJ^GxNZ4rvqD; z7XJ$h+-y?m{dN1pPm=8KHi#GRSvuM0JDW^%kN3mmryD;%-(%rzd*??>?&GJ5cG<6v zg+9M@dhvI*nCiFNb{cuiu714t7sChcr2;8YGxmIEnX^u(`@Pu@+k;mh9Fv}YW44@M zmw1`fhKj`HjNK5%--gK*iSrp}?@>tjcxW1sy)k`ZL_A=R(n$;h8_+t&HPF#fjgDZdj`~125PkMDl;@^hH_FFpUt5j(`uxH|7 Zn6%tE@m!0aH84joc)I$ztaD0e0swxVyrlpD diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadowNew.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadowNew.gif deleted file mode 100644 index d2e68a15e7cd74573ccd08b4ee46b9d45fb5ff99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 zcmVe5~Be{CpdSIx(l{XwA6zn0?*I{kr>JHy7|2`j2 ze{Y{(zmLza-w*JgK!5e#!Kz?roWgPo&oO-HFk&}};2KW62##BWRsV2sxiJXRPRNfU zOOiaf3gs}B2UEIS`LZRtNsxv{R4G#Fh-xx*)Tq#+Lz^;vdQ{>9 zgJD|kylN6>R-IdIcE!pPtk$q!%aR0&whtb9>eL=er}5FJic!tIwHw!>6}nTZQQ_OD zQrn(Z2^w?f%P?ZahF=W?=$K#RfW+!nF(^dNc1x>v zoi_IC!D~ZTMy*UEG+@Fr~722V#y%k`Do95VNnUqS!=xUeF87Y~@MmmnBy#I=s*-At%mSv|2>38X3^%aWk zvnz7wtWc26Mv1GM$(U@g$^tnhnxxd(=OYz}lPWlhHa^Z#eN5yDNhc6D!`sG`0)zuMt~{aj$|INb$3O77TK;Ia#M_n8*#8 zEy>U_#@lQM>yhif5X%cE#?ZM7p_$jId-JEB${BRdNane4mq`mdD^CKK6|hgRMyc(b zC(rA0)#tT!F0nbgNgb%+Z5J%OK4x6q#b$Ty^VYh$JhIFyvKSag0GdVcl8o%x#Ek^lGZ{v2PL?X%iN2Q8kAfB#pVuc#oG>mc|uPxWMM}D}? zxU&s;rm;^i{p{Goo;#{CXFi&tEidmG_)F@_H}j}Ay)F4!L%?(Vl>huZ?$v9rX6vk# zyZyXd;0m|*St2DD(U|&9^_t|pENhAK2+SC`yqb9oS+v97uqt>zj^S*6(y|~1&GUs0 zatwL-fMD=?hq@1G&VAog8sJQ*wG%Xqh6V40F@oX|2WP%6;fsmo zbD?q&$i~Y7F^iK6+ZVByLzdkUfi%Qob;7tmxe;iA_hMljohUL@nURGTOp+m+S2!(R zP<|TRV-;Cv5g%64k47t8AcWYy#8K^xQCuW!QbtM-IWm>tYM34`Ikfz7QkGb9oF6AR z%drh5JHYE3(g?`PBpz@>!g*u>3mHsY!Ow&8L!2c!_`y{^lR&xjr6-5DGr8=mQBgdk z!4eok`E(O=dP*kAVw63z;c}XLB$~yFNXvKCkew3L9SW)0yflvUmEcsQ1I@WAV+!<# zbIhMQbx2PuKFoj#%}7JRXrL8Fi^Vd9WGFzTLptw;O`ip`)gjsV6AyY3Xw@v~#^xzc zI^Hs*b~+kCA^I^RLYy!-JCXnFjj5sr$~>7ejYlAy-_ zUJ6wr%=D=>1tB0inbfRm^=PGn=~lmrRIGw^tV!KwEy;@3vOX@YY*j0k>e<$~?tpu8 zt!od=icY)gH3wbXt6%5Z%fAZt2HhE~VQH{0xE^+~Cr~V7ADaSQLUyv0tt?veYT3+c QcC$ioEN4H9f&u^lJKo`bDgXcg diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_shadow.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_shadow.gif deleted file mode 100644 index 2f49604df553b82bb923f713548b3db81b5d90cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 660 zcmV;F0&D$8Nk%w1Ve0@`0HOr|&d$!ax3{FEq~+!1larJG|Nj6000000A^8LW000F5 zEC2ui0P6r)000C3Si0Q)Fv>}*y*TU5yBuIBj$~<`XsWJk>%MR-r&I{sc&_h!@BhG{ za7Zi~kH{o&5CP!?(5Q4uty-_xtai)odcWYXI16|<0a1c_&2GEj@VI=PJ@e)l9QB`mIMKgf|s0~o}ZioOa+0Epr@#*s&WFEq-d+L zva_A7Or^BBy1Rt7uL8Wm!ozO9xWvfGw8pT?&d;FCz|hpylhW4N+J@NM-rsiJ;N#?M z;^pYy=jrU$>+SH)@A34*^Y!?;_xb#>`~3h<{R>DiRDfH}3M!0t@YlkJo(4jk$gSZq ziWnD3%*gTo&c==)Sy@aNvLv35CQ}kXsj?)?mLOlk`JQCr^bwe-aET z6d=)}{Ei~Ur*tX3rcU8OjmmB+RdiOblFOzcC5RR)67yVs}>sDw(`)z z)hTyw&$@PjHY`9guTfv5()u+TU@zan0ihfmr9d#^r>zVxaQrx3l)@k$1Q^4)v**vC zLv!YnrSRe=rcM2#81g9{%{y!Z?~worIj&b+zv z=g^}U$OBlq_3PNPYu|8E^7ilG!;4?;(fj!G=+mPQXI{Pg_wb+BsUE++{reZ_(8tfe ze}6w&?fEC*fa!_E7=Z{TC|(@1?&u?aIsPbQeFsjE002ANH({Fq diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/success.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/success.png deleted file mode 100644 index 90b70efcd109b084d4239071cf8361509b5d6b63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1036 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 diff --git a/src/Umbraco.Web.UI/umbraco/images/thumbnails/member.png b/src/Umbraco.Web.UI/umbraco/images/thumbnails/member.png deleted file mode 100644 index f6a7bc2249058d145166ca358c668a8dd79c7035..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15119 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 BTe<)M diff --git a/src/Umbraco.Web.UI/umbraco/images/thumbnails/template.png b/src/Umbraco.Web.UI/umbraco/images/thumbnails/template.png deleted file mode 100644 index 1f31ccd07bb7b63c28ce1c909545663b8b99d8bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11883 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�gmQv-%%6Ns8HF_!kp9%_P@?=5WHE1 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 F0sv?9ij)8V diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerCacheItem.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerCacheItem.gif deleted file mode 100644 index 8a441c6261c64582ddae262ffb2d1b0661073628..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 577 zcmZ?wbhEHb6krfwc$UWS_wS$Y-@ZL~{^t3kN8i7H@7&P-|NpdCwI#jF z45Rda{r>g$@4w8}#ZR6*`TO^8&79IdfBw9C`EpTVX>hLhyN_=_e*SX(-qZJQ-+ul6 z^V;FV5AWRh^ZQp!gWuOLU+z44SU+Rkf;IDh|M}Iv`oNt%J3oK@w)@zTr;nff`SZ6a zJ#%Gl_M>MHCV6;Fjf(#H<42aW?T;VdU%r0+`P;W2KYm<4er$4L;)$I*eR7@JmTZ6b z@$<7+Po6w@U=plbwYZ>dN!y3d@5(1EUASQR&!4{+tXXg$=#j^dGP_pnKDN^_$)-0Z zwZYBx|G$5=Rt_sm3a=eMo-jG|>5C^1uUx%x`C@6$oXsb%_7&%S{`BeF&tEl5cRzXc z{^E_t%l4jGy65zXD|bIVd)m5Svrm=%oik^@eEq(0+Z^fgz1S2V^8D zP8itdHKe69x3spVHKlfS_jILnq@?n4`1CULO-R)=w2ESKWm1c9PVMg$kd2FpwM>wZ z5??x#fh{yr#houYHF;BKysDFqC(&Sf>M~;q~Rgy{l{{5K4 z+IyLF{{H^^^z?qL%(!4;s$5;vmzJ(tTGo`6;_dWvoVFJKWf4_)`hQWB6#*c%;gS3N#ca(3S zM@PPxnP+85d8Nhi_3(D1!^d}c#Mb1xdwZtH-j1iXS&v!h)6;f{rq80I;JUh%xzUrk z*!=kT`~CgZ+~t`th|yN*_oEZyv4Q0v0`OXz|g(L#=4ThgnzemZ;_+Z=*&oYK%cmqonBs* zzS#Bn^pZzQoV(Ng{{G*_#eBYl%-hLov0i79y19dc!rQv~`}&Zpp;v`SYMWi!t*!d^ z_npGq_V)Gi=jUOgSAwI%bg5>Ww8{DK@b2~b^!WSPrlz)9U;qFAA^8LW004ggEC2ui z01yBW000M{fPaF6goTEOh-G{fNltq}h<{H7Lm?nhG?7wdb2dUSd67Xq4n<)V9EU%8 zRWc$9K591!g;P67E(bby7F=#@T>^u3Yco_(BN%5UDO-3MgC#2x6Mjk^DrqQoaA<=A zU@Z|3V^=r`S_W@eg9J4jClE$2UQI_#7gmD}IXr!FX0m)?;bDOW5`!4XA+jVu06Jm- Qd;k!mO{9w$2?YWGJKB;x82|tP diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerDatatype.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerDatatype.gif deleted file mode 100644 index fd922b92dec8dede7b09c1a62c415c4533cebd6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125 zcmZ?wbhEHb6krfwSj5Kg?c2Az?>=q1@qmGW;s5{t$;rt;63ABk$->CMz{a2hWCK+S zFfcGXW$d~WW#E;`we!a<9^;o`3S6@3k|6Ss(@6Ihd-#z+epkmrX6@ diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerMacro.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerMacro.gif deleted file mode 100644 index e66d10972c34803f6b3833dc26b62b50652f9e57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 604 zcmZ?wbhEHb6krfwcoxR+<;$0E-@d(i_3HWa=UcaKy?OKIv17;b^Yj1w`SbVh-&?nC zy?y)k(W6HX9z1yQ;>F{~kMG~VpOcf**x0ye(W1h_!V@P>ehcdUWmDwJj|zJv}{Jwrr`bt$p(3$@%l=`}+D0A3pr>;lqm;FHV^< zB`q!O&Ye3KE?jv1`t_A7SC%YUGGW4mjT<)}KYsk)y?dKCZ*FaEO-)U`diCnvyLb2P z+qY-Wo+C$&)YsP^Ja};a{P{nB{)~%@OGrpKaNxkgg$w`v`}hC(B{r&882}Tt62ZqTuPCQclJ}RMn!UA2s zcCsNFLfYP;Qf{5hO8k6+%|VP7k`m40a-M9W&8{A-68g=|>@ESS&C)8i?3|s30t#G; z=1N>#Y=P=J&5Vv=)47#pSQJf}7>%0c&3K*J{PYZ1E!kHwC^VdNX<-u6kxAUJ_z)Yz vWg*=e28Xy>`I*HpoG{3oz#z;dVKU*t!p95bjiUkt5{v@3<%BUYGFSruU~usC diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistry.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistry.gif deleted file mode 100644 index 40d496f7ca0a1e416b18d7448ec52cb9a4df32b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 zcmZ?wbhEHb6krfwSi}GV|Ns9tPMi7k!>{jOkN^7l@c&GZh~iHcMg|6U1|5(JkXi<2 zYmHrZtPH$erWgmmP?XLK8h|hD0()1o$KqpIgw3DtB>#4wcRM{jN7ZZeVR>0 SCsVTKwXZ1gvQ=SVum%9Q{4^o} diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistryItem.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistryItem.gif deleted file mode 100644 index 0b19ec1607fd58d9b78ccbb0672c3a0aa92ce15f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123 zcmZ?wbhEHb6krfwSi}GV|Ns9tPMi7t>+xSdAO4>S;sON~f3h$#Ft9P`fK-6gGB7)2 z?7E|7;B|s!rv!iC$sf+!N)vt-c89KM?a+D^+Hmi;fUaM^=DAH<{qiK<2o}1|cz^wo VrtADChf_BBt}|;do5;Xm4FF+%GF$)v diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerXslt.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerXslt.gif deleted file mode 100644 index 7204e1b54fc01e2a310c26068f8ecace98b45acb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410 zcmZ?wbhEHb6krfwxT?wU^5x5S@7{g+^5xB&H{IRct5&Ug{rdHb7cbVXUHkFl$6vpG zeg6FU)vH&}pFdAbOsuM^`u6SHpFe+|J$v@`>(|w*SFc&K=E;*MpFVwh`t<3>jT<*@ z+T`Kk@!`XVA3uKl{Q2|k+qduEzu&N7!|&g}*R5Ok_wV2D-@mV4zy9C9fB(UNfnuQe zlZBCiL6boTWD>|v3~bvEgq(8GndNk%$4%L%H{ikwk)tAgescX8(w1{wR`>`zYR$Bk zZS8o%YpOl*sFOm3k3!TW3)fX077C4w3Y@~SN_m>FH&jtj%ls5DZQ<=^+JZ;^`TE~{Rxh(wwmI=&8^m_ z2Go~$U#nSP3}+fA*AZaIOc3!n26x>B-W?G9%KnF*f8g1(r}tuyrv07*Qa}U(+3j|R z!yyPlHk&P#O2uNaTrP7QSE*F0)hfd`rw z!G!=CP9&0KIydxsC6QR6>7O*kd|Cv8P~p63e{pnn6fK*;Y8Qxt{cb13=^ z!y;eP-~|dMe}GrZe?xM~rL@=Ubvm6MkH_tHyId~t59oiQDoR8Ugg}SZBx_F~>9_$I zc=ULn|B1e@4>8__P3BQ}cm%GMY~Fr&m-<)_tT!SeSX_HVaqvLrrPD7hw##C1$JV+Q zU6adbleX_^+_0_g&W?`=1UKy7dFtlXjS>yqZ~4-7;>N8j%CpCN!*lDK&u=<5_W%F@ diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/doc3.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/doc3.gif deleted file mode 100644 index ec577f78b5cf98b372ef73ce4054082e3557a0bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 616 zcmZ?wbhEHb6krfwc$UYocJ11)U%&qR`Sbbn=MNt~eEIU_-@kwV{rmUv(=eNKYsjp{rdIWw{O3E`Qn|_{pQV^BPUOuJa+uno$Gsd?|%O5+2?QHFI+gk^Wo1A zU%nqcdScsyA2pMsOz8`t9_|6EB`W z|NQy$-MjauF5LF?+4JX5pFMft7 zX3e^F>o#oIuyNzY_3PLF2LlGuf#OdVMh1pF1|5)AP@FKZA8p9XX>Q5MYR^tj&+Ey| z?Vm6)x4So&i9aq-m6?^#IXAl}k0IA5*hO0-G@RRb!J;Km%)A_YoYsuI>wA_s3JGh5 z38pe~?9R)`Wld&`h>>y^kIl_mbkamw-dsjK$V~FeHFg#ce@ib}HD9BLJsE6n4q7?} zX#%c_Z%*lpUt8tYQ4U*TLd(dL^v3%0X+3ir2qf` diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/doc4.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/doc4.gif deleted file mode 100644 index d9dbaecfd70755c9172fe072148bfc3b0303aee4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 597 zcmZ?wbhEHb6krfwcoxU7cJ1046|xvN(nKYr}(>~iMPg(=gzo;`cEd(WN?t2QiJ zv}DTmqdWHOELiqx|DJto*RC&G`EK^)uEK?ncW>Ku@!W~L^rE9DPhY)!`O>9J%}dTK zUA44-^KGxhjKlj59X@#Q(W6JX@ri5Kt=YPD$Fytz&s{jTeCe{ohYl@Yx?s(^br&w4 zKYaAq)yr4cuUoh0z=8AU&)>fP@bKZ|SFc_@fBO9C(`O2ozUV*sx2Lyt$JXr^&Yin> zqQY&TrkY{rJga-@bjTTJe0#h7D)WoZ7T$)0#Cv z8`f>uuwmoIjqBI1{|^QXvUi!_!&R#M_$BO8@F9&LH0QV*q6aWAK diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/doc5.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/doc5.gif deleted file mode 100644 index aa4c644d632304e4c5c6b726aba0fbf9c91a0d2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 609 zcmZ?wbhEHb6krfwc;>~hcJ11)U%&qP^=rwJB|m=r`1bAF@87@QzJ0rN>C&G+f4+bJ z{=h!6vIT`Npi2s_Vlvt8j5g%7RJ9+l>=?VMH zn|!2gy&2f57|h!Q8q(6vpFe+KllI4tAOEitol;pjXXebC>O$`s7^b6p5|36~*{Q2{;JpHFnpFUw`dGX@KEiK(EmoL43{d!BP=iEe{Tio2AK7BG= zD^cpIG1I}}(W6J3Hf>t7X3h8S-`A~Mw|Md5jT<+vU%!6Ch7AV}9N52q|9>!G7~DYd zCkrD3gBODi$eExxVPN0a;N{xf;_BA!VsG!&tVWMYuuP5_#j+y-Y+T1QZUJRbj zyzct$hVFLm?DKmTIkS3Ld9bf>XI!^%v7-m02bcRn$6XGK_bYiYxi4jOTX4*o<&=ts zpsBm#`5p%z=e;_XCgxHSoVUFk`0jhys*4y{YiYi4VB6>+V5FufCo6W8<-u=*#FlO@ z8I>1I6A})XNQ;|2crfwc0VgJA2Zfm%4|sHnF?KW-g)}%YF*5O3Bz)+YB&2ANDCD%H JBT#|C8UTvG(+mIr diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/docPic.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/docPic.gif deleted file mode 100644 index 3985e9d78677e953101b03cbe2269029c7dec042..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 607 zcmZ?wbhEHb6krfwc$UkscJ10@88d#KWEK( z__Uzg7=g+66hxq$@WY2ljef;0kr%#WbJbB^5`RKTmtl9VX?Amkx{{6!T5ANT7 zz}4I_*U4|{mH*G4J=?Nj12?Bc=A0)x_wMameca0}`0C}$j~_oiec^2OyvJuxom#YL zNq%Wl%i%vw4fzGjU*{G?Ihz|MBzdp8_cz1U+t=UK&D>?}x;4eC-j=U>w{`1|Sy%sO z&axc6=)+j9P^>?leh0b zTyo=K*{=V^Yd-GTdtm*#b?2^LJ$~}ow{PD{*M2&G`uy^x%QkJ=v}Vnkb?eq`*sx*a z#*OROum2AQ3}gYtpDc_F47m(CAl;xiVPN0Wkel7ylAYO}m6n#H25SH()F(dx diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/mediaMovie.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/mediaMovie.gif deleted file mode 100644 index b5f91ce010989aabe58d87d1ff001ed333006522..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 619 zcmZ?wbhEHb6krfwcox9G%E~GtA|fa#$jQkmAt50yF3!itw}1cs>C>lYW@fs(y9)~o z&zLczqM~Bv%$YnqJV{AO2M-?HxpQYiLc+Oo=l=ZplbxN-&(F`!&VJ_18Ch9bB_*Zk z=xAwaX=Y|-F)^{k#Kb30p48XZ=j7zn)YSC#^(7}KfBN((Jw3g$vQkP)%FD~Ex3@Pd zD=RH6t+TT;BO}Ay+&nio*T~3d-MV$^>gp?2tmx?IxP1BY+qZ8`O-)anII(EaqId7! z#m2^ZdU|SUX>HiBVfO6VqN1XcCQXWpit_RCS-yOEOiav@B}?}1-TU?H*ZBDOy1Kf5 z|Nb#BF#P}jA4m=!p!k!8k%1wAK?md{P@FKZpJ)gO5K9nEbi?UMjlo7e1!TE%)N3-$Q?eV`7X9 zZZF*ro&4Y^_3>d76PJCRq(K8?lc6L_h{z5#1&0P!W*-S@M+fJ2CKj%m9To=^Rm7RN gSOgRvG&QoHw5Sj&Y3OL4%~Pc05a86gz=6RU0QfwsbN~PV diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/memberGroup.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/memberGroup.gif deleted file mode 100644 index a2fdf3bf16cbf4ca0680bf052394ffc8fda49a33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1000 zcmZ?wbhEHb6krfw_&$l@@ZrPT)z#-SFvLy$G)GK~L2{Br7dwN&R>pXS*J)vEB_&@j zTgDKoZK-DWt+Z&ke-lhH8qBFWUs!u z#GcQ<6@FpnqtdSF=SXJ9D0=&$T?R6Ovo)C`7CH*TB`4pyuPwVd)fbiYOA zp1iEn9vR2H!_znGd9O6Bd%YnnEakBGS~0w=+Q{zLuO&nLEW3JzMo3}T1o0f)f55u|E)|MN|-@ku<^z^Gh zw!i^9dpX+$np4C$L(c5!YHU1KwDw=Ms8i;Qk{L3tduN!LIjz-6J!~@bR?92~hMQSg z8K+&0vh?nim+v;WykB1Wq`m#(omY;9ryVU@`+^I8efp%>&i~`_V@{KmXS}?2D9Q0V z&F3;ZoOs-!_F~@io{87z%&Fg2WxYw>p!>Dk4h_$qAI4MFpHH9eRdx3M=Ubwa8TcBR zYiCD2?CJjh|3AYhphgHN{$ycfV3@?912PPhCm1-6GN^LOcx+hU=o!|;V3{#tqAZ_* z1mBxO%bgfagxKXGlad=$^w=0(gbdX>g%wK`UMey-BuEL1SS45lEOwOSS9u^Hla%N- z+ggudX5oPau3Sx(Q|1UTxb^hdbL2@#I0~>&)34xboFU|Nyo*PN&8_h9@)I-pt%YJ8 zK5B6H%T3y{;zmHKACskkQcL7#=8227TRAigoSvU#R$*GXr_%ATfQY^n)0fW89;Xx< hn`&}6MVdW1orH>fA~v)qD4V%l(a4%~F~Nbs8UUvE5x|I6Ez5xwY4I5;w*X@;8w+>$L<=Sz#=1-=sQ+&7MVv#& z8VNN8uai}sJ3WgZPn|mZ`t?WRdhzbfZzjz=VCt}_xqacYXYch>o`y$FxqJ6%%j(b3 zQ{Uvb?B^Cp5tJ<2tF2Y<8qzy?udKzAU%&oHsx_X_)O8D<_2cJnt)Sh@Rvw9%_T}@J z-wG~!-NWWsxlJufnRHx3MTM%JokpM3us52pHfh8_{hzahNH8#JiK(_;z~oa z>({QiH$8B(tuf7d;MeuWGE{&Kg?}C{53w`sq}_T^UBeaZc)olo;<1T+v=wuyLbP!-Et}_CaqudtG934uNHbm zTxY`d8_(s;XEbzd)AHXbBwg?z*i|p`@UaO^|G|J^6p$DKia%Kx85m|V=z#Qr@&p6N zMFwL|k%-h&;v9-eJTVpl2c_(%hv$ zGu(XBSJk--`Orqw>d diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/nitros.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/nitros.gif deleted file mode 100644 index 029b441e768f88b84c93ef44a51889326571671b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 525 zcmZ?wbhEHb6krfwc;?IS@9*FD+ZVpNfA`g{{8y=|Iy@ktLp#XXk45Wes-Rl7GLv^C_1-Ili(PyPM=?fJRW|9gxsE}j4T{fn!)+W&8KA90cW^Lp3g1H0a4ss7uY z{P)kFzdyd6iPHODtNgRw^v{Q9&*!y%-rx3ZUE-VlTi>myJ>FCM<@%++Qyq^MM7}z8 z^me%9$2A52|NmzgltA$(3nK%AFM|%qb)Yz5V9#psZE9|5ZEMlf=5B9MQZzH_X%=)9 zah%j7>trRWF@sgplts(N-AlOLT%CznNx?N6}Lq|Z(PQj#I-r0v;!h=^xx!pn1 oSYOPL&&_%UqrEpj!weToTZ^ZzRp*7#?#nT5{rUdmCSzg^(aAjlBnjW9FT${_A3g5nd_T|OtJv%neY4)35 zqV{x8@q}#YM~9m3Ts=O$K>60z;xBJ5@7%QT@sXClfBw`(2_9dT^6%fjvM{kpB_{J5 ztPjkKT|A@Z{MwAh9H&S7%ip|w{N>HPH%BYW6O3-`DD2Hqzq}^l;_CDl&mR8!`}^pD z?VG0r-Q8ZaYgW|W+0m!hWUpJjY(=O0t<4#Ke|PSe*X>sIypb`_fM+P3!R^(nQ^w~BgaOv!Z z!;2D6>|6il)$`&Qy*<;TdkQSy-QRU!Zp`;Lmk-Vf`1SSG&yNp!YNBexxnAE~{pi8H zWCxZVQ$mi<_ixNFyuZ8d*}lU2M;gA~?7evU(5A^D>n8`izOv~0rt+(+Q?Bo;**_=d z=CNfzzr0=5?Rjo>{LlL{*351H_v_Q|FOM#6E?C{|)n8({Y<6>Bf$`KbSJjgj)e6qn)g9xQ#&4Si@Vhm2~-kyJtk&(d~05x@$2LJ#7 diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/repository.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/repository.gif deleted file mode 100644 index 35fd5a8f815ca2151ea999ec269cb9738d5014d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1062 zcmdUu?N1s90EbUU9lDp&+I25o*Ezp+W@$RJxb%z7YNt(>C5tt+OAR)3?Up9hwOdT9 zs|e*q6QeDglNS{Pjyvw)pq@bC0C|(+*oIbPw*nq1b1>#yGFOUnbnX6&J>P%H^ZW38 z*mAt#{Z0Ca?yK7Y`JVIeXSCc_UO<{{&gsY$L-Ac$EkmL`+QOeQ<2(Qwc5Btt`YoP<<5 zct4RyrGB+LT?|v~z1pYKgM@ERp_m9qA{Rct7^i8Hq6P#a0|fK=!gxHPoS4Kgi&Cjt zT3WWdh&wLgmh4-lYBHTpU+cTRy}iA$v9Z4X=M9l$@$RxvEKbri!!UPai;Ih~OWl|I zuM0rfn26IL1arA(=OfWh-g&#j+1-8F7YO)*p_{k9N6cm&2uGuo2{YYK(h{)*v(A)? z%*d$BfEcHyrkyUrX0uIewPuU8>&q)<3l>i#l1W;wP-QYXolcLMEI-C#KgAZ3tE<^; zHlNR*Jk<^wVAzNnO_<&Qha(hIVmxy`i`A-9sY|7jKS=UEyJ$9HgvTu%9QK6*#bVLs za4ak=P!Y->@H0gQLeRq2Bi!LySy|x=gmT4{**b&I%;5N}#~%a@kj-JI=@e?lA{2ES z$GM&7W^Fc&Ru4kPT<-UiZS4d>xCpmwO#UGKAP}54K*(G$G+KtWQPXdk3}&&0!=w%}kdbI_f4_K8A|F@GlcZX&^Y{ag z{{#5{Q&V7-0e~B*dwS(*69D@$&{w(nm3LlW0mX|w?{;zd?^!uJthHRKtiHCcp^jIv zG;;XFY3?}B+bDasoXJ>m&{y^HM+cYHO&5Nse()?bcga7++4$6CR?DlPqQY-ms(o))^K|0-Mh|v_v|k}Dty#Mil8@} z-U>JPDx=Twj{x>60gH`%x(`rRaXuLJcI=<}OWo*W@2hAF9ZJQFW9GGE9skUYh(}** Y6qbk8;RMzx4_<(UJoaDXHK0`8p$X#fBK diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingAgent.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingAgent.gif deleted file mode 100644 index 8d726fcdf47c6816f65f58aacc3cf280ca84f0a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 145 zcmZ?wbhEHb6krfw*vtR||Ns9tPMi7k!!P5RXTEC~~N?6j*31A{dH73wmh diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingCssItem.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingCssItem.gif deleted file mode 100644 index a697b9adce70edac1486044d918d5b718749cd9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 299 zcmZ?wbhEHb6krfwxN5{OckbNp-@pI(@nheC{c5VJSy@@OHrA?|Ds63TpFVwZc6K&2 zG<0@$xOnlRgM)*!gY&xe>#ttD>fz=2^5x4fU%tG3`}W?wd;R@=H*VZ`{ra`3nQ2g9 z;PdCtKY#xG=FJ-)Z{Jm`R{i?*Yr%pARaI4SadC->i6$ndf`Y=CnOQu1{O8Y~v$wYw z6cn02fBw{|Q@3r~77-qH{rdI)|Nk=(2NZv@FfuS0G3bDlgZ#w6=HW22z(Yr>|HP6a zO%VncpGbyni9t4f?3*7y(s5|wSS4bt7RJz|xj?E$#LZ{nC$Cf~HV$Q%EgmMq$~r3I R>>TR4fnD7_z3z?-)&MEOaj5_R diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingDataTypeChild.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingDataTypeChild.gif deleted file mode 100644 index cc557f784bfb2e4908d821fc9602fe1f7a94dfb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144 zcmZ?wbhEHb6krfw*vtR||Ns9tPMi7k!!L$ipVnQp`2O|yub&V9&jg8s`Saz=m+#-d{QUX(?_Z$6hj;JZ{rK_m`?t?;-@g6+{llL> z-@bhL@bl-JUq9dc{Q2$2kN4lczW@65-Ip)#K7ao3F{ra_T-MYlY#0?uZY}~lf*4B3Y`t|?8fPo00_>+Z^fx(zT2c#C{CkD0!2O@Jk zdUp2di)0;3IXUmbikzEMmWJ^+DtxRt5qBoifo+EnN3_Na%eX1w8xAHj{cbxxo#W)? z&ckYDK3Ybsx@|H-g;GX(iOsqSl7jLEQ<;1EIF)r-xkSVagw$pq86-}z?LWk|+mA0_J0m7IQRm@9 L7Y{EVM+R#E3x~%i diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterDatatype.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterDatatype.gif deleted file mode 100644 index 2ac4ddbc683a2847f940c4d967320f87913f0e9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 587 zcmZ?wbhEHb6krfwc$UNP|9Jk58#m_6n}7Vo$=P$~9X)#V)alc+=FYo(_1dMYS0_%M zdhhPTt2b^mx3;a{xT&e7<@W76ihFE<;K9t9bCxY%vUJtT=g*%XKY9A@-Mg`|@%QfCJ8*(q4n?8N! znzd_>9zA~g?D-ovZr0T|-MMowB`y2fwHw!N+`M$@@}@0YyL%_hoxkApnbW7voSrv- zVO)ID{sV_|^NZ3mvR}M>v0=le!-tPNd-iP7i^NS|NsA2Ro7j;dNnV<;M}?Mn>TNbiBCLs{P@njySw}P=FFWpea5Wp+@jyVe|Puv zuUxh6=&|E#*Q_~m^ytf%FKer6XU(3!eC4WbJGO7zw!N;g`RUWA`T2z_RxV$%Wa)`} zuNN#>2sEF8T%h=qg^__Fhd~Eq04Pov*jF^<-6|9&nIC+dYz9_z7%nELsV|-3;=I?uBs)IE&QK!2-rvjg vmXN7{vZMD*EzzD7gClMp!aCeh1s29mEY4=ECNnrTC;5n}i953~F<1ite~cB* diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterTemplate.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterTemplate.gif deleted file mode 100644 index 6c79a948fef3ad6b4dc922ccf7c72f9e6e78e8e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 235 zcmZ?wbhEHb6krfwI3mjM>({UK>(~GI@nijr8NYx3Ub}Yf&!0bk-@g6p)~&yP|Ni^; z@7uR;>(;H?K7YZ-OP989-|_$6y*1OOty#0?zO|oltOOlFME@`<% z#1jw&4~`xbA-3fZxkQ1Ah>9phY3Wu-lL)m3ZG0PP@rU)F^J913yl=kwX5Z|amXsK) z!XrQgs3Aa6QBiYqv&;1Wa2DXJ2kx}Yz4sd%8-dT;+S&@Z>bABvP+JGQKER!8X=wpf zesI?hJpS2C1*r80Tn`EyK2San)cXTD_a4$$91dT=;RV(HrluyiTbOYiT=xU+4&zSq z@>OmGAOJeG9#B#So$Z{Z*nfoD_YoMWf(}W`n0zw=k zMq(sFiA=6kDx(ppSRxjQr6P$`AW^DiQngr&JTeqkvzr#HleLMDYQdET&0<+vEfP5# z4xvLPr0h#X5|v8Db4aBEh!8L?8;d&yHfD4?gO+3nyP0Oqlnvn-aU+$(YEY>3?-s0d z2d#|}2HO!5lvU)!X^})I=38n9>U94NwOTvS44X_o*88u-Osb0}Mad*X<=6=l!bkI^ zXfw%~!CAM% zq3#lBYkk<#+|>AI!-M;O_g^@2-A( z<(taOmo9$&)rBw5pF4Zz^cSZ-KY8N#v5NAt&)i3ke0sR_P)Tu7;lTs@_kHs5M<4Fp z^TGSO-`llw$Gh*m{nndrY=3>*Yg=F4vU$_S4eQqxT53x2+&Qyn>1NJ&Hfj2_#Hm^= zA%4o_Nt(EcYE`T9WdmPcf+TD=Beixb2mgN4g(gwRt8LZbWj&UZ=C?%VVm@*=$v$jMcq TVUbDxQ(MU>asGgAAz diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingTemplate.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingTemplate.gif deleted file mode 100644 index d84b0243c1b62c2627436f93ed8865552d2cdda3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmZ?wbhEHb6krfwI3mHYcJ10$zkU05e2gXTDfvN( njQ;L9c@54iX_NdW2kY~&a56HovN0Q)@-JGXqM@au$Y2csmLhXu diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingView.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingView.gif deleted file mode 100644 index 74f64b87ce1b8b8dd729545eaaac8aad66fa0f1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1644 zcmZ?wbhEHb6krfwc;?5jcJ12VfByXY_wUD#AK!oe`2G8LM)j1xfB&XcPs?wfpHVwC zziCER-PFH-|NQ^|IliR-`_J$F^S1o||1G(E;{X45z5&7aSGVq;-L-4i{^OSqp1$$r z+n4mJ$?raX`TzfILG$dc8LOr*-BHjqJEdYuYQ>bK@`;(X)AE{T?>%;A*RI{UjWfS| z`<_}g^~bN@x9(s2|Nq70g&Wgrre@U4xPSlt`D=HR$|nB${rms_55Io>e)Qv4q}24xJX@vryZ0+8WTx0E zg`4^s_!c;)W@LI)6{QAO`Gq7`WhYyvDB0U7*i=|m)9WTXpJp<7&;SCUwvn^&w1Gr=XbIJqdZpd>RtPXT0NVp4u-iLDaQr4TRV7Ql_o zE7k*hM=v=)SHB{$K;KZ$0OTc@LSJ9}N^^7Js*6j4QW5UOYH)E#WkITbP-=00X;E@2 zP`NV5ssbzLqSVBa{GyQj{2W*)24v)yt$x9Sh)g$qp7K}fu*U7p`ojhqlLMFrK_uvi?OMhvy-tSOs`9Ra%paA zUI|QZ3PP_5PQ9RnkXrz>*(J3ovn(~mttdZN0qkL`Ox$iU#AzN>ZwhX=7~#~b4|I$^ zC|Z%C872fwJ0K=J(E>T}WS^P`OzTC!M9ujB-(O%h|M}zlx36D5fBN|0{kyksUcY+z z;`y_uPaZ#d_~8D%yLWEix_RUJwX0VyU%GhV{JFDdPMZ`!zF{kpYlR+RD-nPikb&{kYr=x+;1h3v*zS4?}jAkrS3Wts``Kq&+$ZM&Jshr=*?yJLRWNxb_ z$8p=0)tAFYSwPm5*KooPR~KI;U29=}4Lh07N8bDm*wDlwD5I~zu*7shr+_#&k3>R& fqR={bQMMNb5f2FjxZsLGEb) diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingXML.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingXML.gif deleted file mode 100644 index b7b1e5ca967c6a451ac72c7ed12b20f0615ec26e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 268 zcmZ?wbhEHb6krfwI3mrkX3d(QprB2gHmzN|_S?5_U%!6+@ZrPz_wWDw`Sb4GySH!O ze*F0H-@kvKK7IQ8_wV=b-+%x9y>8vQ&!0bk`SRuG&z~DMY}mMQ({UU zV8DO`6o0ZXGB8Lp=zzpPb~3PbK4|Mz37b1jAtz_a-hwr2*Se*4JT}l@6uIWCL-9^W z1#5{3rZpnQk{nInx%oK#x(s*TVKA9|NV$O1MRu0g2kn_Ga(>1td(%vAH8TBlOJ|hf zE*DN@ajlILU~3f0O%e~SlVtDWZHk!SB*HnFyIYuL2D3QVY_=&vtTXgl&C^3nqy;x^ MQqj=TQDm?N01bnD$p8QV diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingsScript.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingsScript.gif deleted file mode 100644 index 556ac66c30335929e5d98055946a4ff77f1ca191..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 553 zcmdVX`AWiY0KoB|T4{%6_kAC9-}l{bQ1lqRNtgugzy1s&qo5ujLAR!viyYQ469N$= zGmF}En=G47SJYedd4ex|H#XO2=e7*M0A3N0%jINQmLy406q=^}e!nPwW-=MC*Bc6j z7=|H9G9HhMqWHnEhn6Fr=bcWc-ENO35{mL23PfrobdrJ@w z=jD~#?anC*&kJ{VbmTdT<9Hwtcz^%EFf5zRQWO=9MqgrYHkDforGh;)AqOqmyQ3e$URFw`a`~V^z)>i-k diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/statistik.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/statistik.gif deleted file mode 100644 index d526390307f6d019fefd712ab61ea5bfc0e0f976..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 305 zcmZ?wbhEHb6krfwxXR7|1poj4H%^;rJoC)=ug8D=eE5GRNN}hC#h)yU3=HfHIv@vw z{KUZKcc8hzLr0F~#1bJ-5s`TT8jcrwqLyDhcp&Nk-`ra(u4%L6FHC;5UBFD@2JjG|Q-Zcl34HPN!HZ`P4=qaGJ3Hp;#)2ywi*{|_@aA&o z#nXqD&TinzKE;r5`tgyLXZs4zuF1A`X*<6*uQ+cEA4n=g;pikLEP{y}vl|(Sv(8k1d(dloVJiMv*=Kt55y*G9g zPRN#i{O$j%AOD1AJZDTjdvAC7={4DVrboYd`FQW_=v$jJ%<|VBpYMNjcZKx6AFr=0 zdbGd%-ThshMQ1f@E(@Rk|MTMmbEl?ttCw*%-7Ki9L8 z=jw60{x9O|KUi)3e0@v7&P@xyyt$XV;jHM@uch4+vL?*!z4q(+z8Vw#g4LZq`{%^0 zUD(r>YqMryx5ba2b`y^C=byQ}sc`R{*hhz&md$P!So-zJR_0E{{H6jmCc#Aw&&d4 zUbJ@W=1*rDFK#aQ`Q`2Fo2&o*`qWaJet&n}hN~Y$mwjH>=gV1o^}xK?bF1TJHhj6Y zwfM`6(;ZcAHT_+-X?uEe)c^hamtS2S8Ev?EN|3;W7Z7Z#$wAQLnNQS`@>> z3SAQB*v`IXQ^2a^!zjt{%RxY?zXh3DXtDv^_4|V3f)Nc?a>xgQ&4!g@|zI0LU?$euq2jAQBeRMjtQh>>MZp5Ot?%ATQ oZka_B+8Yk?8M8V3WIo>6&&9^O$>MWX*oP+FH3bVy3=|lw0a;tV`Tzg` diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/user.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/user.gif deleted file mode 100644 index 5b67adae9037eda67e1154987f0f2b8202f5f81d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 998 zcmZ?wbhEHb6krfw_}55HNTT{+$L+iPC~6kJ@l%3>lbc; z6z#yhl4^~;Q}&%aeNRZbK-v4GR?zOMh7F#@kKKZ2iR(=G`}gm%l}CR3{4MXW&7$ay zw8ir4H=esSe@dx8tQvG#TyL6H@yq;{{qj0>rdbcP1CND7&2H)3a`*02ZKJwLGY>>h zdn;i$-7jc@n%90A>owjTuRTMiyG1Rl?>*2vd9RH5(!)n?8m2v2u;jRme(&aO=f%|8 z-oE{;;C|@$?>|j_yEVeED40yqi#)0C&>NVsOW$pox${(Mlll6wXL$uu)IzWD3a0vW zzBYB8I&aY-*Oqs3Ce!sI4@XS5>i+!Y_ul>2bRsWSH*C@~X>zQ6Yvnf8%yW^T zWRa*`v5Mzb&$f5&VRMvy&q?S_Pi;DU>-GzuqEp9?U;qC7*TY9|T$|rXsgy;=&S>b^ z=H@p+#dEKUN$d6NkNmpcD7frRs@eDK*?U=wB??|A^;4b(_rFtjS>Lky^Z)<<8AbsO zLO}5+3nK$VFM|%qY*3zH;5f`6%_$wvRLZ`#QB@QAr6#`yIPNXX9yDU<;pXS*J)vEB_&@j zTgDKoZK-DWt+Z&ke-lhH8qBFWUs!u z#GcQ<6@FpnqtdSF=SXJ9D0=&$T?R6Ovo)C`7CH*TB`4pyuPwVd)fbiYOA zp1iEn9vR2H!_znGd9O6Bd%YnnEakBGS~0w=+Q{zLuO&nLEW3JzMo3}T1o0f)f55u|E)|MN|-@ku<^z^Gh zw!i^9dpX+$np4C$L(c5!YHU1KwDw=Ms8i;Qk{L3tduN!LIjz-6J!~@bR?92~hMQSg z8K+&0vh?nim+v;WykB1Wq`m#(omY;9ryVU@`+^I8efp%>&i~`_V@{KmXS}?2D9Q0V z&F3;ZoOs-!_F~@io{87z%&Fg2WxYw>p!>Dk4h_$qAI4MFpHH9eRdx3M=Ubwa8TcBR zYiCD2?CJjh|3AYhphgHN{$ycfV3@?912PPhCm1-6GN^LOcx+hU=o!|;V3{#tqAZ_* z1mBxO%bgfagxKXGlad=$^w=0(gbdX>g%wK`UMey-BuEL1SS45lEOwOSS9u^Hla%N- z+ggudX5oPau3Sx(Q|1UTxb^hdbL2@#I0~>&)34xboFU|Nyo*PN&8_h9@)I-pt%YJ8 zK5B6H%T3y{;zmHKACskkQcL7#=8227TRAigoSvU#R$*GXr_%ATfQY^n)0fW89;Xx< hn`&}6MVdW1orH>fA~v)qD4V%l(a4%~F~Nbs8UUvE5x|I6Ez5xwY4I5;w*X@;8w+>$L<=Sz#=1-=sQ+&7MVv#& z8VNN8uai}sJ3WgZPn|mZ`t?WRdhzbfZzjz=VCt}_xqacYXYch>o`y$FxqJ6%%j(b3 zQ{Uvb?B^Cp5tJ<2tF2Y<8qzy?udKzAU%&oHsx_X_)O8D<_2cJnt)Sh@Rvw9%_T}@J z-wG~!-NWWsxlJufnRHx3MTM%JokpM3us52pHfh8_{hzahNH8#JiK(_;z~oa z>({QiH$8B(tuf7d;MeuWGE{&Kg?}C{53w`sq}_T^UBeaZc)olo;<1T+v=wuyLbP!-Et}_CaqudtG934uNHbm zTxY`d8_(s;XEbzd)AHXbBwg?z*i|p`@UaO^|G|J^6p$DKia%Kx85m|V=z#Qr@&p6N zMFwL|k%-h&;v9-eJTVpl2c_(%hv$ zGu(XBSJk--`Orqw>d diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-bhuiness-cl.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-bhuiness-cl.gif deleted file mode 100644 index c21ba30b57efa757213c063811fa5b4239d3c8e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19424 zcmWh!bx;%z)4rw!=>`R)73mfMDFH#cyE#0N&ZE2gr~{GXZsHA-Fw}0&H z?Ck8!&d%&S^X#s?qMWdZ89!hb@&f=+prUZnBk{4OObL3yf`>;%3KS$G&5B}nF(UP{ zq=@rja5W~2@utrRXG-vAbkruc*LB; zkM^VsbD@p*WekE-N4V0VBAMu@fN|aowx6FT1u!BVsm+uLJdnzQHI>4I3 z!;-?wlq}MXF4l`7z=jfOOX+1!?rTQsXG8f#huGVUEGL#FCzd%onmIY}MRqh(df3Y( zZng~cnrKD%)#)g-a0m1;~ z9Qnc3={0ph4J}Qf9GzXAVcmVIPpU@D6ZDVSkHZa-FXam$dv?;y zO);ps)azxCI+h`BaxNbAVCBfvq<{Mdo+6jHCho_oQLo9KoZTY(GI{4ABaU?}_uZTB zeT8J28Ar}Kl|2gQM5dDGG7=vYQcc^Gz;x7H`b2ZxGVHmxzD8N7H34GZ;et7sq7Bos|_g`QX&^t z@cdmj(+`8rDXA_1_8k5 zX{z?1=S+h{1K6)LF~=pcQVD%vRGR(==&ME|84{ulQ)z0cK}WiAlpy+S_1aLZbJwFokb#Pq1wO>V3s;8T&Ze zsWLdLYtX}JSOsDB^MQ7o?6>{JlA5MG^{{sd!(f<9f*duICQnd*uQra&(_-<+K z1ale1S<^$si&YEp~RQ=IgfS>ntUQO;vg_dcj;}DgmdgYLotwCUgmggFK zWv2}W0h?43_wA>$t+>Hf_<}3D5X6`Nbw^+XA%{7#Cg_@n$oT($>LHBa`q}?Ho(ND! zoPH?=(c;N+1`suFiH$CC)c6c|eat@jf%Zlcg3z28vucnQfV$MQnD3}*(pjrjZ+H5t zai|SXv%zJ(HHaL*%#T4p3XQQik9y1Oz8LEoP>^Ks@Q@s-nmK(n*jgNJZb(!KvkNwO zw&X6;bgTlm(;y?}&~3$wdxjl2f$YH!56KmIhApBXv+IIpC4cp8tO&X|yFeBgJErpH)cdh`Ng_F8r#WT*`t9+T8_Yjox1dHA@>@ zPzu&+DSCulQoic5FTps1a+&5G}$h`CD?I%b%R!{c+O0uJpz)Z{742Cd3BY2gwo;* z)`M}pI$=~h!Ww{0eQxvJKXhIl)@u^)`r{&KWvOL1wd_0Ogst#| z^JC6?0&oSAI+sJZOp+H%WOPB@ueCP(@v7k+mx0Zm;wO)KZUUgEu06x6QJ$E36Eh+s z%4VI4e8I&iNxD8q`Ai^(zbZkUTlpl~E0Cv@!)DD}iqd?diihY8*eoNp9e$wh|80CtX(QG#)m(-CVIA+CW+;CdX1%Ov9ylodam!S#QH6Tl<=; ztyQ~=Idye<_4}nqsz`LUVf2$1#xnL2!5dO8Z{+$3Vp79s5$8H)!p?ZrQbYSnP3C41 z1$EvA>L1}%x|oh$vX~E+9?oe^7nAiHYN7W##=+M_2%gB1yI2pZY`q z0mBz&Uqi2J8ELC@il#KLziPc0nUGYM@hs9A-63Pf2dX8nYALriqK@$onDcI@eT@0O z#*af<3PhG%|D;iO170ibwQ3Oi_mc5HIhyfwx8R(0?fqj!fU}dh0V=J)tpYb_cN6t0 zlYyPrsRuh6dRiUkdObx{1^b}mtxf**6=xR4DOk%4Q?vgjW#xwR-yQXm+@?;gF9IUG zwT<02{XRz^+{R*P8+7sc*K^40YVJbvFJ)5(0KwdYPP>>rnosZ%8G0A)aNknAWo>{9 zk)~Bn-(+8VAob^In<$x|lU+r(D898X878Rh50#G*dskda1PHsxHgbddt4y&V?U%m!v*;jbrRaaiuiy!Z%w7g zb2hCbU*s>fQ26)P{_7ay@CFhE96+N!dCF)9;#_d#jR^S%<9j~rQF^eBoD2RV+yd+~ zJ++REbxF&-mYP=G()raAcQ1!US7hG`=={=&)Zq>MNgOQO)1Vl0?O*>Fuq^LnuV(S= zfPX^A*JTLu@{f6c`WF*EB%K>rq!ECJ7Sh55S|y87>k87cdE+H`5h8+Uu!F7&gXXQh z*h_uMhyZ^nkfS=lOQ9!ULLNU~*RQZS{zsd)b0bPp=zx~L(BUE8CNL0)2D{FCO~c-; zqzS5E%UR7q%o4mt&Tz9Y;Ca}r_G6{NTCgs}iqP)2Xu@>f459K`h3X#Wz znW43}c)wnfxo#GN_lv>)LLj^Zup?=}zB^>lng_GQ*Z&_BAPhp<`cbGw*j+(Q65OEa z&YS7_KT4cw|MHBIxNNXS6&^*d9r`hNfXiDYbEG3agzK&S35b%j)TRK$m2zWKM!da( zhPHmyvvFXWgP35fTeaVDVj;&D9GGM*U0PjC_}+eb^|q1nE%t%Fh zQ{ea(1IPXP1QiyDdy9q-)O%4U!*q}US~(AT9r*5O+6HU2E{$?fM?ByL|^6EZjBX?0GnjkL=#+hSqt+){=+ig$v`;C z07On$G+7~*0s+)Z54h5X&Ikp|*uIU>RS&v>%A+N<5dO^R&U7DK4oCEXYEWjIv;s<( z(olUG9cTn8z$R2-Rq(AE`&&sOzmoyqM_1rWZ4L?yuclX^OKOUan&Hl3ibyEZBRPPH z@(C=DlvSp5rTvp|?k@Pb4KQ4a!2bo1M+evi1LU{E6TN`fd$u~Y0N&pKp<2L&t|VRp zd@lk;{S}OzfEsrXYGBW@Lx9z40UHshQa7mDcAN^p*#_+Tz18~8BR1JnP-i=0(;D_! zkQZ|p&V&G)Y^PWbKunOCL;yc)jJ?IOpt)@*#h5E~Fu+bY(HfaV-2m`lOFC7ApDVr- zw}b7rx~c(WI*IdUUi#XPK&*Ss#Hk#PMZz>&rHiQCm^`7@2=A^12c1^VK+mrpKk9!qp3HEbYHn538&ap=f4GBrLlqyTX9(9Usl7SH-Z$F0%)!cFb5zwr3ArB~= zX2j$c2ZEO%bWrAOhRmaBakZobm;vC*%aE%~`|5hbYo-d1o7^7xcbL#5WlC7xAGhU_ zaz|~{RaqeW4O(S6$G#-B<1W4(3=~C%n5ToGI)WlnWp;&N%i5#iUqMvI4m-k9t!cMWvQ?I1&<3n(T26 zHP)5P5UBc`Cb+O7n4yHiHGs8BE1Q_Y_S-y*+RA_khzMicke9dYD>vl`y;ji(DH-_l zXd&wauq9iJ+Ytw&fuQvj3J>9r_6|Dzg_7__{YKSDbVwG5187DX4o9-GN}^QIU?l@M zqZb%?WO$I=z=T(o{$Dps$!)_mPX~w2F`iY^4^Cb!>Q5Shcp6L;bbd>SNEkQW^EvDqTF+gZ6rg z-r8;ba3|_6mlo6=s>WzJb>=h`yBGCuwf8+|AMSd?#A$JPDL8#C#nKaGf&jsT0cAdI z^1a>L9Q{Oq4xo6qc~(C|M-_i-4d4Bfmo^P5)(3J^hT{9!7G2S-BNe`3-6ZBKFMvCj zdnN-YFoD4BE*wlN%C~JpX`CWq0$2;Et7v7J2!!~Ij;%rXcsdzuD=xgDe6?QDXOmN0 zQ|P-!H1*UzHZ)uuOu_R6uHzc9lj^vSgqtKxlsrJ28eB|FJ1(N9YdgmHbzywyL{toX zntBq)6s95u#_4St5&=tQfvru`3J;r?|4d0{0dSBSLQ)g1SpX0`O$(Fh+X1jg4gvwF zJFIl_E@v(LX&P?8orkt-X`>1oz&RRVj{s@-fL7hXAgPfWu6`ZI$hQ)6%@5-hF#zJ; z_VQaO@l`D=BhbbGN{pWNgOx^=L_+MC2D}hrsIVH zFO)U+BCFJ*;S{Rh%byB8M{_tF%f0lBZl%d@`eXFmM{eLtI83gqk(d!EA=+DE2YbR# z-{it*N5h(wv&N_Tgb<*u&PMvqnc3rYRwN8ZtppDVvrQ<+W}LyLn;hS0z&3@6RYWav zWBMK@l%fHwrm*>q^@-EI*(!|C_S9DF#7CMAjgK9^4lql=WWRVXLa~DyzJ1gQAXbC1 z#lm`x)&{s&E875kwUbtK3szMy+PEFJa_|Ig9}WHL?2B&YX*s%3c9JN*?q z^l>}=r}I{R2ga(q9MwB7rgx1c!0PbrmYnuqe!xBMgP&E~8PR*raog{5%7vtM`uw&R z{2)A2S+2_scnApSf5Vjcrn|!k{M#Wp?_ShmXH$?2LK^Keoh+U0uKth`W~ITc8;GO_}J!0Gg9UaWiwr(p;-3K zJY}mrrC>XC{jpok05)(j!v=s88o_XC&q|dyZPj28%5G!-FvJO$Dz@`V z22jt78B6q;w!yl)|HU@f6{H62_AGsfdsd$3O55N^KGg#A#ZQK<|3Su{CCPKZ2TJ@)=<|DB%}X&6)Tc;6ogMVL62-00BZk zjLN_mf}@J+sLkx-3Y?8f)roaKV4lJ6nOIN0+UrUYuy(*TgMNudzzKfUu6zPO|M9m$ z4VZ!vpwE3&Dfv6F`|3h}zG)(h_l+Abd|ns|eW%j)-R_JY{xp9m|C3nisMg_z$!8^d zbJe8lS`@M1s(V`mKUz`^AJAL11}vr#-f40Do%gw&r@5V9Ymtu4J9`Z@UBB`nJl#Lt z+=LGINNtLhOp=!l=Q~o)EleE zDG)LG!K8L)MtOI0`f>g8G4G2v9|B}e-*oWh$3Y(8N6q9GL(`_yztaGaN&VQJS`K4k8V)V#8`3={W7(Dg75xn2#Inx$YS zgjd@J_CZD?9Kg#OL#|Kst6_1jEWg0Zd$;&Wo1Zx9^*^j40lN&48a;dcIbP$-epRUz zceBhXHS$v%x}NK+z-K2Xp8`Uh+-PlNbuUt^Ek9F{?8vp!=; z?GaO#?iP4lG(z<^f{;~>v`!j_lgJ|^*Yuk*gXo8Z9T4v8fhCLQd?k)-(`v{a$H@=1 zfZ-rjKiL8R1D*5njQG=kGqIeuPy771@~5@Zu^Q>ZGSB5r-S|052ufu@mX&}PlvXAw zQeimNwf!@W6gYbq?GX&{7UMh0st@vjA?p7rst|{>1;CD8ozo=g`*w+LD80 z?5t($C;x?qG6SxO!y7`6!Pl%(I8qKK(vzU$_(>`C9^wbW0MLFm)_!0u#Auvpa!(aZ zSW*HUD=>kSV?8ok_5yF8`fnAgK!N2IQy+g5@-^Dk*{rR(DXE=RJn+w z4w|FY{$FZoC)10&2B=C9=zZQ1#m_rFu*z!;6X*(w$K|O=>IftgS_@*8*nY1{RR8X) zv!;yWOsM4MDZ^7ol)Q|!)~KGgjkeLUHIszyOHy6in4a2Q-b%_MngTSw!vmJscf|tN z?y2mv2dH1%dqw(Q3mx9v%i~{;3dZo*e}a(?-g_{KK|SzrI-R?gzATq7(`@-@)z^qr8v$vm3+X_ zE`AYe0;q;0vp`s7T7tgiHe?{b29e)2L1lj9A-FK0#gm&svFctZ_G208h1X|NVPGC7 zqTlSYZ39oqw4HpYgfx5mv!1XSuJ-(e$Q!NARJaaqtBTFf**4y^E`pY?6-;L}&Zn zar!DPV}SKp4c?mtGxBh!0A;8lg`rv@3{2do+oALIo_v88x8X&G7Ko1~My(h4nL1G7 zkm1R}b&FV8$A?~_B&3Q`i@o-IgtwS2HIpNjE$>h64_eOXA>WNzYYO?#%d75C9#X0d zfo!_hzt*Zdc&Wpu%Y^ge7ye!ltHxul2y6s#$<8HUR<^?jbc_L8+A#(sSCWjVQiQ%bBTLCK~(H49O8 zrB$}Q=3ypOJ?^UDv^3-;7T-)37r*NhjTJ`pegzZP+^EM1w@dGZ@@8DTy4298uze51 zL0%%7y3S^eiPv}C>$%dV1ius-v~nc`;p--xuZlWA?ZRH;eq)46C%doRY;H~6_&uE) zF1fFK`6|?7C8e$(s|Dj_tHW2?wvLuu&5jHDg=@JD5i+3O+plVMV{%geFO71_yV|2~ zOr_h&B5Nze+N(18{=&J&0FOK=2(OqC$qu+@FyP}TxfRCCo4b>fFCk>p<_Tq)fRCY6 zIllk>J7PG;Q7$f|nZlGaU+QCeqW(A%Yg=jO7ymmcg~})3^$LR?sn}w_A2Y)7C-hOdD@?|*1p38muy2lQ(%3-jwd|V z?4X$Hm+H0$x`hLed;8(POx9N7T_bO9vC&aQ?u@q z?~ef^5H~_(z~t${ykZ0h{4j=Uv22PE}T>XChr0=l~O=ElH#bAOoT zZ)KXq`A!vl@6*mNC5$gl$h!a1z`N@RjUaOS%Me1b;~5$(N8u!+``@n9yVk#~eO$n5 z;UDa;UkR}=x~>#yF&}F3HzW0byXtG1_wyr-OnpCVhu|!e}wnZ`Dd_RUk6n zHZqkma8cekUDx-%$jDgAPc_w>GdLoP)i!`Kz)mngSjbrEDzxbXkTxN#vK8q^;msxm zM3V)EFMMvM@Kbj)3j7_t$l^A16CEgEc$RA48xd0`r#@5ig&bd{u89J?e}Th`w%6g5@1w;1jh-K^1xlsr#N~H7(f*+fzVaOfN6U* z|15)VM-$h>K@#XhFShVQ%5WJ(QoCo^u0jwsjz0?qY%;3Pt`|?$mMH9HY$=?~bQ~_^ zm8c;FgxW>M>xw^X(?#7x5aXmi8;itIG=5f=P$rj(%WnBxD1em6nHLSIU=5{b3@4%p z|B2;XKMJbUN)w?BZ)kN^B1$6CNi(tYw_y(=mDd{3N*`Ww9$HFYT1+P?fnIB8gxm(X zl?9MuId3jzM2R?CwWe>nXV9xTdMReSMy9y2huJ6sv)K*l{sv!11YLTjwXlVfO2vOZ z0?}Tja?W{RmqK0H!S2V_h%sJX#xV7TEaPoxm)(<3pGdYbPmDS=yXzP-Dv~`yl|7-C zWh|9M_c!o`2(;NQ?awHXPBG+@T9y+#_>VAzmMH3-ZqCXrTmmPTVI^@@KFh=-M~($- zjLtdykVCJQ?y?MB5y>UOO8V=S!+!)tr2a2q_B2)Y>OmItE~}eLOaB1ai(}Mpr>Rl` zr9RHM^~#}D%lj9ZSkjhfBo*>cFI@%$W)_9yFGFd{jb5V@xmLlSNOgJaeP)gec$L(J z^s|?&3%R3Gcuxw1Rtnxmc@2Cp*1v+h_5PO0TKK^`S>dkWBPvlM0kRKG`dI#Lh&3Fi zJw6pXNwK_$x;_2V6>wQQcTF#?Vbob0_sO|&EWV#qOOa8T@UFP8OiPid1XCodxD78O z*LrybbiONj&X&(YR7$91OiPrs`XMW`BtKO+y;)vMpISGPx-9aph=v2Km{6KpTB-^t zi}sFAIVnSPl&R~N(F&z!v=_M^LtZGBy5N@9ouG$V69beg5O+ZyO5f`3z1!|eV#dpJ zPRbfH(`>!-s=ZPDEag_WK+ThK%kkoDj&HE}LO)azsTQa{B4z1S+6qVX3a;n+3F?wE zaD%G4Mz8dFD{AL1YGKvGue|({BQA8+L$S8{puFmMwTjwKYW*Z>OTS=&BNsXbl(~`> z5`kQM!ew|Mf2qSQPii1OaE64ZV&_sV3`@QXu944p9ZhsyR%Tt6B9ICXMJrbS8dJyG zktx$#rO%k#j8!*KRK>7esDA}yNJx;m0`h$Xk~v5TO#qcNLBgknl48-Mn0g}WMxxb9 z{i`g=kH8+U1`)h$G0rAqL}RmjlcH&pcty5+bawG!MTSn;uu!uF9?GTyYVF`{odt1< zZZ4)Mb*zAT+@-7HH4Ah!GF-(6hBjoTH<=S98A~;(;I)oZHNv7ARPN)|Q%i?S!d;F^ zBB@KVMB9>Rk}|N`;-lLd$Fkl;$E#Zq*_XJRYrjr3v)40}nu?FsC?`+`geej9LF$B$J*#ixm+#{3GZCzH& zeGF%Poy5%Tw#()7zc#Mat7F>pAWf&?$$JMv+!` zpMguf7Cpe^5Y6|$%HRKFLnC4)&%~Qij@}oX;fe|4)~DUa3Rx4UV+ouX;WLbj=eKjN zCrl*uOc_3AbqqsJhiUAXq4&e&aLu>~W8AYkepO=XJ28q`IfN@wzaO@hq?;5O_SHv5g5}ydcU$A@xYMU$Ow{ZIhSFMi}kvlbZ4{Zl?4$vcm@v@^RO(a)=+N?7VPa$^=lBk z>b{6x$u}Aojvh-CHA4+M_CSI_<(SLDy2(@=ujc0n=4?qj7UJaJJD_yx&&6F8EGmMF6?7c;4_Y zcJUs*^;2R3sJfMry|tv0C5YZ~{bI8f}1c=iGC) zAGuday0Ioe;VYmpW@8`T`#CpBsyavAxQ-F0OEs?NqH524dp~mO;0pfK3pVbTkz8?uLxr!F zwRu)(i4KMB_E|3)TvdU>=#`okur|*Ur7>9g^+9C&L1 zbf=Bor_GYZsEzuf>eF0F*d4)6Tf$vh~dP za-#b3Ge8^=GOH5W0RXNT2iwgjW>U#^BW&j7Cvh$PS4H!f_eFKMs-6Zm)W>)C%opMJ@| zK6Z@VCwsaguG!PZoaiC0H{f9Mr;+yVcs(>w{0n&Z;=1ZJj2aD86+g5FS15| zuXl{D?fvHZGvxEfhT+`y`yWS>8RhtahRPGQnx7P(u3ay$^}nB}#Q)Z*0b11l{_B60 z6njbHbdwIh`AvVRQ?o)}y%PQX@Xy6>faJE!v+XXQD|fns)OfJyzvJw!eVJ-t8sQa> z3HaOPZEC>P{>7~>!%Z&Xjhf_sRn49S`gfhly#V6rUgI^)g6Jmd-+e>RJvHJM{BHnV zbBnCJ9U;7$=()rCbXT(VP#W;C@a{+Ur@JWQ$MS&t&;Nk+HFsqU2YPP~8s9y%Ki&U` zzn_+X&37G^Z$8!%{=2xm6;ykol>%>m=|vJ^3kuT;T2KoM^IKT(3o}><2nsL=Bnt8; zC8Y^wXJ_g0C-U<%WaQ+el$2*v2hj>pQ!~U-TLcM*)(h8KGzd2d(uOog@Yi+sMzpn7 zjb}G72u{sRMOI;ETXKe$8t2-I7-o`oOZU=?au2?j3GN=A9@SDOXJG197k)9&GW626 zMzHXPu%UXTFF?EnEOL{@VL8Zw+^Xuzv;JqvYRYKwp{-st8u-4=69JsFriWli1VZPTDbL`|4_sN?Oy1om9P$Kflj zB*E0Wg*1UmDR;xN(}j@?v&frhyRWfo_<|z~+iiTo-%%pTKO76$>|tV>oHuK!b+3jD z^LCrXsa%|Nl{r`7q3Oq^UhNhu1WtEJKh`D{qYFscZhsZ&CrYc{VP} zC$wozB-~cQ_8l1wb}ZcV{%OH1u8$I*oMZxQ3GQNFNc-QDE#%RIK)Q^9G?Z*%^iNPW z5cqzlBoB7;TL)W%n0K^SnV5r(F_4m)csG>XeV3dwtkxEfHF|KVfIr$1P#AgNl(9!i zbMzwCAKPg$-(S$zb31@A&NDyoj*Mj&c_$#86Oc3LS;+0}h**wgqN1t<>fV7|FbMNpL4BA`^c)~g=cKX-K&xv=Ov42G+%ml?@nx6 z;_I`VIi%Eb zW;K3JD_U79g6rz?9F6l&iSMR_(Q$8{{h5m6q~~1Tm#Y@}ZfsKp+|F_WcpeLZqqit; zR9eqYqe*|8*{-P4bf6f>KWv8YC$>1{f&Wwfz3A+LKrGPkpc`i2y$Nvpg_OB;|L*!l zRD5~jFO}7eOj(6in$U3U%i}H1VKMQqrY}w0Z36 zj*!K&5$`M55h3^pQV=0!tNauq%%T^Gl`2#Kyv4P8N2^{Eq`g^syX^;oAa!>c@aA_X z3W>MPiLs`#7Sv-ABY7&$v2di^_`(nX765pV^wf@{w2kyqlDp{IDn9M>VEG{t*}%bMOKf1>}d#M@2$5 z-&56Mo-xmBV?9BS&Z$K)p`SGqiMZKb3XEgIiRaORG^i=ePs}O3!LUD2>sWsz*!7(1n{%w0_O3U@M9au2Vy!d2HU&zBlw* z>O1E~QPg7ls@0b-8$O$tlBu0NDm8vvH0w8u%@yNaXD)B?^zf1HozS?86H!L}oVUWc zEK}#fbnDyyK2!D+{xVq~y$#2(ci zwv1<&Yz_hep4cJS%IINgu2X#538@op9Q8K@k(h}uC%t|@hV-cqeyg?*R1z82GvVRj{czh=J zuAU;o*SCbzTfaF-IA@!PE&Y(Y9T9l;%NX3TQ)+!zVg+|e(0M#;wr*~DH~l-8Gv`oM z-fhz1wZ14Out~wDYGuLSedcxGv*g!4Yjm~lElW8UU-QIP|4#qyzL&W0!|Ghy_-ENn zlY52ix!>5zz3Ch27nrHe>9$n5N%Y(9M7J;fvjD`sS~1Prk<* zdEWCrlF!QGgHPo@-7$>PU%$J0Ahgf(nc3{VEEe)>k%)I+sPMljeAE3?FW!50Wasa2 zxP(LAllO)R3Nrgw;@A5;KP*><`x+0)Yo9O28~2Y7N1uA`Zt25CJ-eUvgg;$xT>9Kb zKP~=CEAA;O3kZ08%6*0@@qX~|ZWQwC5khVXd4LtLn0t|PB_72~ep@9T#KIoiLVoM4 z{;(T=TuYMbZ1!pqe%zzbbD>c6BaIeT`YdeQ0p7_;EWKBQ7D^kuy|?ETJuwc;Bv5> zZYVc)q<%(_&~j*POVpc;a8>LmVYcA!BjG-55gNh~Vi^&sw!wTyVLiGbE;qJ1wxK?V z7+wXCk6J`_YYe{v$h{Uz^(ZF$2`^@&9h(0aOY39x)HCv7v0iK18u&83`y@ zEY=uw#1?DpCeAP+o}VplzZEV{6#srSaoiTmcQlT`E|$O!i}5&aX*p)86^>68@?0bd zt1RYWG--*=9wL(P;F-)Y7SF7h45o_BKZG$VV!4~TTeGIfjm63-KBastOR=`W60c3! zvrQ4d0xMFbTK@%WwWS!Yq#CcJzB5ggV^7l?OSM!?u~EdbYfEvLN^?S_nc1bdZ^Kkp zQY=MMP4!Y-#!|f%Q(Q&TRBqE<6XM+zQ*}i$zNcqIwPnPtWW?QOIGg%J--10%eE^hc zk&&6{Wtq-unKG1_9#@$uR49Na*0+^Rb<@mu38?R^nN`QA#9QB@V^pplDt`r)d+S@w zjw)HntR2fp^2)4VK}F$YW!$0$GP80NQT8~TZMOB_?4z;FVw@-Bj9#{OWLD-_wiy-534!IL zhUIA*575VQM__@8&~8#_&|Mzb6blrEc0yu-MFAc>K@e&*RTP#R5)DRTy&8*sDO$j+ zU+~JifX%)DY>y&oM;AxtbKw>cE9F0VVeyLsaO}~-s|Aqp{686me{8Wh_0g}#V`W71 zpNZyw#X%F@6)M>0i`%1AaEmxm1={+B(qpm861^T#X6OIDFNg>xs0aQPq;-r9( zqYy_aAKShVuf5Qo8tr*ij5i+pf}`MTS;E|j4z(?osa!7Z-fNU2z@td1%% zR4SbvFLvsxZ!Ac_OXF= ztsbz}z|UDPHBtBOzJXq>k!G!iIr^zyB&$JCtme%`BauTrjHXf3r%{@-QO=RpOXgMwMK%hM)CU^u?Z~p(^^NLS}mFum-{;Vidy@QCNYDSn>)~R(`N7$ zNF%zDHmi|$qTV370TJEoE!Gk<(P|N0YxA-BRdkCxXOo&iYiw27T+r_GMDDS4tXtD~LqzBSpWA?3b3D!ToCwFNQJ)=JZ1!P)Bdu|smAy|<&zm#5R0 z2s&ucIkJ{BHi6~E(>eUHb9}9Hl(TF2W7l_|&gHECz4&ykuXSzScWu*jPgHb`pLQ-q zcVeQu4(~gsXu5_bx{oG0Cmo*Z2G%k;DW<(eFzjOi2V><6y*%XAKiJq%pa0F@pPd zO(CieAUVL0lK7Cy!=R4%kh=2FbH*XP$sy&*K_l8Bv(CYf>w`Km!>Y7H7L&tH%EPLY zLs~JzE|o*h*+UMFBS!ecE}g@0@nQSPVV#FT2k}vFyY*o!$Kj}l5hL+Y z|Fe-4$1$6jvFPkEgyGXz+WKhdDDK+?IB@3xBN6aU%X9qOxIkOyHvs@dquODZhU(NDL%<&t|3Hr?m=gf(A&53W!Nj}a= z(alRs%!|ZAW&NJ!6>{d4y5^NP=2ai()#w&JODt#_Eol2Kyx#!mcP$uhEEqp7n9?np zODtL%En536+U6|UcP%<@EIK_dBIuS}B$ix_mfZc8Jad-3yOw;p7fG&`kaWvI63Zb* z%VB=Y5jo3IUCS{W%W;p(33Mw-5-TZ2D`|c!896Jcu9fVKmE6Y_G~H@}#OgPr)ndQZ z(wx=uuGI(vQ1#<#E!|qZ#QzxDwPwGy)||EWuC>mMweH8YUb^*uiSPR?fzH?8Z8LV}X0~N7cr$%I3Dj z=8?qaro_|6N$kcoeDj=pYX`n{wz0A8xA~vZ<|)C}PwwsiLAH&mtt;-$wezjpoXtDB zt=q2cqsOh2sV)4tt>=d~f<5eEwSbpRn9^gy<;1V9zTsz1=POR=H4fxvzTpYp;9pMT0nXxcF5_r!=Uh(c z6@KY3KHnwI|Ko~2;8p(PB%bMLZt0kQ;ZXkPc~0e9F6niW6YuWzF7NZ6?i+9M z9#8Ne-|q}R@FK766@T&o-|qYF@fyGI>%Q^?5AhZ+^8#P;22bw^f9@e~@cn-98~^kF z&hanr|L`Cm@i#B?(r8-;e$3U;NnL z|NO|W{ls7V=&$_dFaOhD|LMQ}+YbnGFoT1Fa)*U5hKq`giin4dg^-bvkA{oS1}&j-8i`rhO;nxUnNbHBjB!o$SH#>dFX%FE2n&d<=% z($mz{*4N5wYiw-V-rL^Y+}h*f;Njof-Q?xvt|4Noc zZvw4aui#Rw4tb`0*fc7^og>i^H0V_7%(^&f!j+kIsz!!2({h!&v7=U`H|0)rw=r(X znTi9hZQKxVOn)E&BaO({ap0~)GhYnZutd z-<|#T@$l87H&35?di(U-t1rJFKmY#r-Dh5R^{pr0cmWcKAbJFr2cLig;uqh14pPWo zg9XZ$-+K$b2cU!=au{HO8zLxTfhHW5KU3Zk$o{s?BF$p%?rl0Euqs+ylZSuKkhE?cOn#BTbkqDx8} zD4*!2D<_)&)fp+1p?|8Kl@E-Ems29`&Okf-EY5ifd91mMl3c5?;U4TUc@KUp?87z|d@7G|p6O}N^Bt-%rFJ@v zaIO{)o$b5?&zmW_E`Gc)yg2t8_pF!JiLb=K)~oTrLXS)ytKBi$ zU-Ftbi?H&!Z<8i(4YAIhwi>dsR5yHQhoI)`IFDxUtZn0Y9}9ZBBHNm^)J8sCDbW{( zZ8+Gt?%8Xq%hpUY=7V<4uB?RTd#laW0$e4W9{WCQ%ct5{|76@pBC2-nG$;JBx0*`+ z?ZZ*GjB?I!*GnVQSC_1^qHuRD{pV0~3v%}08?CO(_d|Vd(=scJG5hoq727VxGPF&r zVLu~O+dx+|-c4?G%ln_e3aB@pZ3|k66Cd)@)~sG7Z-VS1*tSq+t4PIfPUlmfwob@F z*13>g7c7?K##cJz-HTH8QsByP_$j1uFljH#p|f=7zKvOHU#%*Y%(@4=g0bv%AX{6y zrsh8=0*hRz8rIpSq`I-iFm*wc)!^inq)}mwhPCQh_x49RGbWHt3oPIe0p&UYPOxh7 zt7GmM_`L(tDRvQrn%YO3U&+d>LY zeTmXuGMt(5T#2=JQS6Ku6zR1D*vUm|Fk;JW|CaiAsWBxgw3x?R&+InHL_NaPdaQ)# z77Z0Xm?-sYaFZmLQ?Z-!z{Go;c$V3&7l9-HDV4OzW zQ~2&NaDW=IP%(J34=%P3Fz%|*jLOccarkMC#ioE$2_G9f(2bT&967fG|z`H*vL*cuS3A>W;^@Y(2lmW zr%ml@Tl?DB&bGF<&FyY```h3Sx46em?sA*^+~`iXy4TI_cDtL}e~7od=S}Z=+xy=5 z&bPkz&F_Bu``-W$xWETa@PZrs;0RB+!WYi)hCBS>5RbUTCr;IQr%v^%Tm9--&$`yP&h@T){p(;4yV%E0_OhG(>}XHB z+SktZw!8i9aF4s(=T7&!J=^{6c+b1u_s;je`~B~L54_+9Px!(c{_u!Tyy6$n_{KZ_ f@sN+aOn9scCH_!Rbd;aqW4+H=^4&w%* diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-bhuiness-cr.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-bhuiness-cr.gif deleted file mode 100644 index 2027ec37563391ef1c4ecae9d70a4479b9cf0c78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46684 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�PS2!a^AW%EQq~!xjryt|@ddv3s2|0q z?e?~A>w;hMivJ&&pZ0-o`{Pt-=gay^em51<^c623p|R-EU-8Pn z{7uXseA5{e|MOtYH@czo0gvx2v=DX5_wmJf`o<=h*yG*8IoBUS%(>wlUJCBkCT`Pd3gw`tE{Q4 ztFEuIv$U|WwY0aapjUa2q==l1j*^JQj+Vru$Gw8epv=A!&%vY36Oq=^#>;}!&CuGz z;M}~vl*iHI>EhppxwVY{wYR9axb(WM`m?~uyzHP9t?L%h8?}DT8ueqAte-GrrzWYg zv`U_;3AZd7igjq!1XdVfeYBM&%c)rBOujP~aw4cnDm5N5=_{4VnZ;;!+~yL2PE|Kk z6mCwG?BrD1QljNSo2vna{~+h8z?K^tW;i4z0wudURSfSq84f@NoZ4H zgUC_MTCUo!V-FJn%u|wW&AuJ?9!ysiDkGiY5+llp5}&Pos>+IuOb;z(Sgq(`RqRxr z;c`+5g04%dQN+-k6JFgVu~=l8ttq;m*cevp*|KX^jO{pb&(|Ddx7OYBr%Rhgf1loM z+cwAG!H;@l-m|p-bk35nC(FKi;or||yT;CZ5AxZ|Llg_v3zXPg@H)?tus zYD$@p5+Pdu=#na$x?8D-c2s7fTPnI_kF0iSD|A&pxtp#+O2g|rzRKF`qof|VC#u9Q zy6UWrBD>SGu0p%3uwfo6WUA5%>#H=C)-=hs%bf<+k-G7e>9!-vQdSI>2 z%1e>H_!1l|$nX}N?6=-X46VoNhHPwv?7|!&$NQ?RsJkUw+My#C^1L#=w*norsW{ft zi_u3b)HBaQB~8tpIb%z+)SRxVX2uz(Y0EPWKbfw&#C?qJ#95nW^nj=kf@vX0FTJ$W zBmW%#>%4IXopdl4($sUCY@@=oLU4zSGtCSCTR7O`)`Mx{R#U9C&I~hRamosR-LS)$ zPVRQveN(czTW$M&^rD2rsw&imrv3Qid&Ajz-)5gPv&txENV2l1zlL_&n3m+a>ssHv zCMmTa!Y%Ez*FJdHGV9&Q?tc>#y3(Qp@3PEwPn&&|aI(HQ)@XJgamo&JNIvD|V~#nu z=3DIiW3c~i%;!wMi$1)A-<%!Ih^D;||P-8}4g`;;uzcRKpyX_qvfYizf0;^C^qh7#;@#|PK--uCWyUS5InRT(I8g}K#y*3sP<<5t zVc>Ka#>4RW@P`m%oYpFLKpbxHg6x|F=4?o|bLEYMziXY*czC^(&5nuF(P6Y8$i*%y z?tvG?;QcI^MD|%u8Xoi@U!n*^@A>U`HC&?LP}ssa;;DOh1YZOT=r<7}(28<2-ouPo ztNeNKkcj+R7#GRNMp6!95u4!@^Vq2)7Eh693|a9OSSgA*5s{EoHDPOz15eB~cCsL1^tv6hJpCMg|B%wmF4lB!hYs4NLfT@o^V z$9p6zlNqsTe$f)MY#%m}*i15}QkdYBWiiLeNMIh)aa|N6FP~|~ZbCDh@EoT9BgMH* zBw{e0;UuR%8;Q?+s&k#Zbmu*7*Uv=m^Pt=;s6i2`!Eq{7m<%PRJ0BX%h+34Qh_q-# zIl$45di0|p4XH>+O45>=^rR?FsY+MM(w4gPr7(@DOlL~dn%eZHIL)a}cgoYA`t+wj z4XRLwO4Onn^{7Zqs#0zGho(C9sZfooRHsVSs#^7`Sk0>M$4lPwn)kfuO|N>_%ii|7_r36quYBiA-}>73zWB|re)r4Y z{`&X701mK#2Tb4s8~DHoPOyR(%-{w)_`wj4u!JW};R;*$!Who5hBwUN4tw~+AP%vJ aM@-@poA|^iPO*wt%;FZi_{AL#1OPj{sJG<+ diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-blog-cl.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-blog-cl.gif deleted file mode 100644 index 523323d83458fde5624d8d8e6dac5b6e560d641b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18564 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�FVy(g9Z76`*@M*w~+}M18(33{up^?Xoj8`ek$38 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 P(w4gPr7#U=AOHY6|7X+y diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-blog-cr.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-blog-cr.gif deleted file mode 100644 index 7446c64ddac696992eb2305c36a14ac3a282185f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50756 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-img-ie.png b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-img-ie.png deleted file mode 100644 index 6e796ce9cba90a87c6eeef831803191f1499e74a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3915 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-normal-cl.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-normal-cl.gif deleted file mode 100644 index b338d7c4199709f73ae93f190f010d4e01548453..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18097 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 EJA#A(uK)l5 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-normal-cr.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-normal-cr.gif deleted file mode 100644 index 494adb832758f271496533b29319d348bd4e55a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46703 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
>U|H@NhrFpa59XG+tW+VrM4&8bdz%F~|u z^rt`#s!)eY)S?>os7Ot!QkTlqraJYhP>rfor%KhTTJ@?}&8k+T`iHK1^{ZeFt60ZM z*0P%QtY}TETGz_fwz~DLaE+^6=StVQ+V!q@&8uGb%GbX7^{;>ptY8OA*uon2u!v2p zVi(KU#ya+~kd3TlCrjDNTK2M-&8%iO%h}F)_OqZ3t!PI}+R~c#w5Uz3YFEt5+Sa=E zwXlt?Y-dZ`+S>NExXrC@cgx$}`u4ZL4X$v9OWfib_qfPSu5y>l+~zv>xzLTSbf-(* z>RR`@*v+nXx69q`diT5F4X=2|OWyLD_q^y$uX@+Z-uAlpz3`2%eCJEw`r7xt_|30= z_sie@`uD#84zPd+OyB|=_`nEGu!0xN;08PR!4QtHgeOel3S0QX7|yVUH_YJ<{9 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-personal-cl.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-personal-cl.gif deleted file mode 100644 index b338d7c4199709f73ae93f190f010d4e01548453..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18097 zcmWifbyyQ#7sofo=osB0DAJ-R4I+v(f=Y*gN`olfqhW+}ZZM=qk8T(_I)%Zg(XCiW zy7%|q=RD^*=RW6;^E~(dbH4X;HME|~$=f~zY?AH*07wO{FG}2qM;sA6bdRV(rK&tZ z{B(&D%n|(brK-Gf!i@RyoN;#;3YEArBv^Cqvq#=xNR?vEkzh@{!_i!;N;S<9aA!sR%! zW!TgBAUU_0!UQ1RTr|m&EJ!6THxBA7DYhITW^qaq_$|g%N!B!RmJmUDe=ZsiPU_5u zY#tocsgf+gT(rf?+;Ac0L|*z&+%)ieZ1CGm@uE!0!c4ic9P#4J1u~qOLQJKPd6Vz4 z76>sX^FfLq^Omac`R#Rc8*ADs4O1ofb)SIL6;+QC?tj&Rr^!1NG`dZ)kPrn`i`uqLl^6Jm|$=N1YE7i@Sh1C#sOQ&x@ zF0B!a?Y8YQ-)#2LgMiorAy%i4)jK*u{E+A( zQ_^CN7e}eX)S~pH%7vi)K~Fnoo7sRDe?`H~eVA57Cb%n1ip^#kBz5hjP8UPZapbJO${tKt3j-!jsMFqD0D_?79LcHh+Q>Rj_d^N1>n zVrV2FANYJn#kJ5OT z3=PvkTuYwITLegtS~0TUTHYlLnetFDzdD-Q$XD>^a1^)l@7%UNho~n)+=@v| z)({W#f*-B!+QlACM25NTVQ^dC1M*p<-ebx;aE}?0bM8-HY5x+wuy>aB<&&Ra^AgV8 zB61&YaL@JSt1SAPD~M*2i~8QyQ#cGlOm083S`!IN)BofDThwXtVJ458I^5i{Xyyjy zSVXOVx9SU;E4$-5LO zlBfGH@hFPt+i7_cB5~Q6Kc=HO9*@2Ve%=fCc_%YGNGOLM<>y}_yc42kz^F~&JZU7D zF7puwRQ#CTVn>8OIw2;jb|>gwdMqEQOEOTy4HM`(Hjv_C)Z)coAGOxA&7Rx*D2qs! z{iuIuKOwA~VYxh#d85t1%_U7GUb=Ozo$!=$^PrPKkHLjoa$3~dQlRyvfkYEFK*gd@ zN+U~EeL6<;(q=h9DDPN-YW8HmmP-K}i+!H=#5;E-23!qtQV!`C`T0g;j?Z;*m+=P=(bymh>Nq zjFKK_RY6a5K0YGT_iT2iv`4-1DSfG#lJ@oR)e|t;%j5;^st^|-c@R?NGkS_Vg*u!W zk}Qo6st6TD_5q3k*#d+QfS4^lO1U|>Z&idGE>Oc;gdXoyrrvi8VtA&CN1Y0}9DEKgHnX_-dL}fTQsb#Oo3(qpSCPLBNy>iC?O-z#KE~km zlESlBO3Fl?KqKnOAuzynvjY~ko77cXvO2az7d^@Npk?u07X`w}uq5|niVqb-;FS-%ENZ%o; z9`b`}QBZP<jnN`cJB8go^sFBQ!#Iq zvcF1|Cy$~KfRb%`wGnuiLD&D^GIg@V{22TW!-Ls}b5u`unVHhr%AX8|NB{XFJQ z;r{H;AI!yiusxErk)FcBml@}zyDB;(5x&}Cpb=fQf9d5wUav6FAW?~zyibFhE>>F3 zx@%ItHU{X;J6f41?s7hvyMQSDQ@VNENj&AypqvhhYTX7JJNS3Tz6{GBuq5mk!=OF- z9**kD@1+ZKyZfHcAtp%oNr=urBJ;j5+~g6us5B$?(!TH6mm*M4MU0Y(Ma>)tNDa4Zyw@IUrDa zre~t+2EuqRB|I;2d!_pp%tb(oO;@_ncg-BvpRcf+hJC`hIm!LP0BE9HBwWVE1nB>) zkyrN`UY%?&ZU#>1OqQfIP=kdBvX?C$RcEOyew$v{pR4r`g%my$k(M9FnJwKK= zx#}~-{Pg6)R9s5}nT1rh+V_^1nM{Fm)mC=Isuuvn+Ad}zFT~H;f#(MW#TCI2?S~P} zUwXEySZD(91{HIGg=E`$?Dwwt!F#9w~D0N^e6`pKh-Q_jWJ9`uW=HK%b< z%rZ9>+F>7m^(~M#C}gF7Plzg$XvQqe2OM)yA^*~IeGs^CF%k1Gr$_gszDH#*;I2F- zG(WKIaoacf7qUI_QbE4!itA-rvilMFbHko0{~l!%Nn^YHJ6wYQaU;QhH9Y@%`@L{* zTOsocH*ur!fKovG@Vj1Vdry?0w9kNpuQ?#7xx^=Q z0kj_z(EKR8-iXB3#LX-Vx*Q!Q9ZU2klKbi4ylnUU63d`+!aj(*9$H!MAURTVg6B`) zfGe#3QVBTz%;!$BC$|)7^WB%`=*TjM!1uDGjxmuO>b_eJJ_3s&6jG#oj-Q?az($%R zOw(X~o(a$DmJ+044(v6TlnM>X?!hs*eJfEqeYa1IrHrNHk+! zE`sVP0*V)X)}jH*5I^o|kn%K?69xTx7XK_3Kx^t}CJR*Nk9;Tx9CQrD4!gZ4_Z`B9 zJ=~4Up$N#6^L6$M6h0>=2D8PzFLSfHo4~OMnwACXVD&JX5n=`jkIobN7b6sg0o~_N zO25ygi(cBX@qQH1^D@AuSnrp^kv990iH`B+{K;vi38I=X+Bi$zeNS_A@=%KpiUREE z7jQTG)hAzfWlW;qA~?5&q=+IIj|I?31;?2Nvq#&;n#Klc#s)gZO3J6bi-|G70DfRo zI8}WO_>;|5L8|3}f63ztHFOc9`L zLG^xqeTxZ`CJ~phBm?IO`Wlfva-aJap>{Y>dW+XF%9{q6^cdxn zOCI;#DD^Q1F+FQ2oqs8tt1X>ZIQc0wHI6mI(=RI;7jcaW3pC4yUPLIF+VyMZev;1) z^3RPjOLWyrDU0>iHcd3k$*hYbvCzz890{9Rj7wn;&Gh$4T1t6pla9%96qHZTSc=lj zaTHSp@lobbvIZ-*1Z07cEx&S;9_HZ%dN*>8bi7+M44vQgyjsRxa zvxs}hN_pTr{%m1YvhY3RJA>@7^1^Qvh3O8TH7GMj;z*92G9E(mrZ^B{u$XTn$RjOK zJ3BGF=%SGB5Y{CE)y09tkVQSiMW$JWw=a=>j>(f0+3!@zba4s9gS^MuWd5efh7|?w zW+lv8WDi@T9=DU2qw_2kfIJ?dG^!vOb2sy8Afs97Lpf3&)y%t`p${v*@J#1xUF2I{ zB7#H{V}{G1&ROeDhzQZb5Cv3tI|2%ecONO8KZDo$mF_N~gz>->D#S^-e*g@|V^hTM z^u_iPC6QZTq+Jf?%yYO*=9jN1b}YBzEW0{@eOt`Z5NIKPd$r|JQq)qDg0}y2&Sn9H!oT|onySrjb;y{O z(VP|yukuP C++W5}eM?#GjQDztp10y;W3Y4VfY#uxt>ZKjzf7DF(_Y5}#H=t3p{ zkZ{;^*k0`x-%2=y?N-!~T_x9<*S^Eou8NZQqiSB`OPL6@t^VyKN3blH8WM{J2Trtt zMe;)%FhyQzlvqn16;dm(qxigPH2@_dmIwg=1;@%p#_HG0J0+JoTQ3oITrFl}jlux* z#G#nUn;gt?^s-#P*c5P-oswTWE4*UKO_Dd6>T>w_v{5gP$0qg%x$l^~#swp2tjc>wS0 zk{Npd(~tUS05!(bK;!8e<)DEV9mNk;23D!i-xbkXuC*8V8e^K8P0k(@v2M%bZUPmW z-4)yx*l66@Qv@S5_5f{(^~vd=zmENQBVg$@W;hSy ztJF+G09MkB`CO0kQFYbRV0e=|t*8fGD{35RTGE3CTE>PkmL1=hfwEE~J-XcwZJJJ( z`;1QupC=Ty1p(tN#%QMdpC}C&1Aq(T#J=ZB=r-MEz40dT>;9R};w4=&iG;zv)`8>k zexv1PLz;eGsp_?aMy(*=417Qt+IKvT{w3Du7C+9eOC~GT>vG&6O8}y%vFT#~Sq~60 zr<-$s$Ri;kL;=VP9hE!)QXgY^ZV-aio%|;=cTbY|-N3=4WxNvOw27qG@M%Osqf}z= zUtO|CR>Q+eBNP)Oz2bAf##hS*LU=@Msn4CZ7BUqxKOsFfyD zw^9BRO#StY*#y8CIzJLP+dwnhptP`cjX_y9ZY@_&QOz0N3=NLY-A|lRm6*|Ao$IE- zUW=2gx=w(OCLlK8Yq0@gZsOvR?xKO)Kq`FdVifS4W_}GmH=?+ddAO)6KJaLK(bahh zDlu@Gu*Azha0;K2R$NNI`YP)&ow!mhJ3S88o6%7oVUn7uoWMOh!lCt+*)XN8$7MVx zGu(PBcW5!Ul@}bk=4-F#>A7cX#Ygfj=08cyF=LixB<9+0hWwRht`f!#gI8Y#uPStn z7;w+Ad93=c4)!3%gY|%lddsf@aUYcCg_UP#5@$zg7aS7T#q?(XC9Y50Ai`*YjLUb5xQuvPb}3P0bs&dJHg615N(zXd?IatOiU~cGmQ!48(zwHEVkMtKjN=wefxG5Ky4X zvU&ok2y#1CdB#$uso!eBS#Q@^708POe6c=!rw=rBJ1o@S<##)*aX;kOJFKr+VCDx> zO%jaOn#sRyieQ%8*A577t441787k|{)3`I*#rHP@Tq@hayju{DDFM|zNHWP&_bs1< zJ|0!#-qP`2?CMsk#3pR*nExA4g8*2QBuNfAQ4Z;6M1H$Phu(4pYp)$MBLU+117y}4 zFS`kh{Jq995u#pZ_ZhgpuAVKN>5aB;!C^&4a z0fs*S$R?9WO^vkBt@}^@ zlp_3;nHqU;52*fuket6J{$`2%^YPg1hvrBK*?VU^uV7r0A>%;E$-h^QYSz3_d@82 z=UhogBHsW%=|Ic`051TPbPuS31TgWPMu(Dy^KA+kT(GWhrV`JYrOtU(FTB2;7k6*a z@t;s7pJl%Ic3> zP0K+dkU8*l5#}^1=B#vMk+kSoHeOlABq~P6h$vZsxYp*+0MB)sHCVg!XZ*3bYG=-HDvHoZ`KQ8J`xA1e{^w-tO~rk%HfH_aa-yh7#|`If5f@g)KI4(blFR)KJsA^`}~!-ikaGp1PUx#(>$?rhdB+ zHf0!hzb$v$)M+O_$JNHT0txGiA3yIqJ$E-Id?@5wVvnzy!P!?di7Hgigb|x#=mhW{ zHlQ`D7&_9rtrH8mmX&#*o(9%V?WUE^rYLFN6_^awz=*Bq`d+D9kZ8u@z!$ z6#QV-Bq}Hq9BV5i+x02Uo@(vuwtem+R%Fz3A(5|Q1_!UU+zO2ZjguQk?!Hwxi=yl| zd;8k^8O2Q$h*yQtgU#~UA^SE^dF3R-QiWNbzsx)kzq|6N&G11qh$D*V8yR6V-jF&-HPRV+nGwc|Lgud5xD5FMjOi(C%`dzm6cE zg&kElFDQtmu^E3of~p6%IlvdBK>1c0ZjqTGd*9W!RO1D%4~Bm>sXuf@B)PaIhSsQm3lxdO6!loG&K;UW zsBM=hrU)!MDvua>eU88XTuemHS2=K>olYO?D9a(r_Q?~G;)+7+7wYcIQwjey(4B2+ z*UJL+JBs@Li$*p~v_>L#*j-CKNrx$Rg*KuoBw_q>U_1=p6>|V600$^AwF> z(`4Mt$x|*x>WIZxju#)O)LtJs)E@?zHze9e0rEjAV!AC5wLr6nav+qNPS)?)5%)QvU`v8jmPc{^V}0Z+ zJGKNFiXUeK#mkab%XU(CpFO9)IL>kK+&zRc42dN+RrLz+=rb{JGFXkjV!9U@X1AhJ zvvLq;4PH05TT`AoWbAV8y|q{bu$47SIx1{q*#Z1Eiv1{@*0JQ_EyqPQ_}l%t=`4J} zMr3toKVShuf@HQKz2)darMAC1u35(7(Kx}oe#EuEdAoc5m?+`?5X7^JG@|8S?YD^_ zc|-Y>tzlUG8$hSN7ihn)MmBK@kSQ*tc@z7T>%l&Gml$a8H-i$Mql(61Yk5@-r^v;w z8!oy+!31O@xedD&Y$ptAWKQm?i@jyvECyoZQEk1)+w1eE5&;=lM6ElC3caaXWgZYF zBNwQk=^fSM7@elI6w#ouTTBF14QZ?aQjOorz4M^pfIrLJFfi)R1Ld;I-3}Ke)jM6j z{p2rnZJr@5w|E;=HQXWusHfCDe$GT^K=!2mW%A?2w@kY&8av!bs;I`WdHbbE^U0q8 zGuLqWa5DyWdqWa>srP3=KEvde_u|zwcSZOJWvU9l07QqULJ-?&QfHYTz*_+OvjO2} zx+Jr&(k+V~VRE$YBR{3-bMGu++Tgxt-vD9#_B?K<5q!*(1wN)3*<5{FlfWG?JiTZTi*LHdTl5zGuy}^_*S;bS+t&T5ESM?!>SbSdcE}T zAU%AMbu>QS1if5-QVz^CS&(-nD-S{M{xv2i@-43IIJh9(ejJ;Ye&=Pk7}I<49Vlo=$C zg=#zO&p47WP(Ro|ej5eQ-B$s0KS#N;5XX!|a&v0v z*o9!&#@CFb<=R{}^vQBMLhd|x$jASBsAdKc2HFJ5P%nto8i$$^8V}Ks6&CELmItwz za=q25j*5WVsGBG8ddeo|L{;oH!llaL2K2H_<}2Hfmt!4t3i1rPs0IO0L;cPEY`y}# zCzGM}$#MEUsxVs_`N-N=Y$>F|E5qkK(Ar^QomE4+NB&=J8}-$0IS3^lEWZjUJYkG#tNnal@WJQi<-*zre(%ibdEpY-d0$`dh{sxT+T zhN*kH`q7WbPj(;Ds=qfH{>~1#@hs^-F;Q=~Y$|B!F|NFpa`M;QZeWvkk4u~FiNSTI z71^dSs&!0~loiT_qq^~_G(#SO_PJ`sk@B610Iwf(%HK^tLjj-u0UXDInx13ZX5XTJ z<$Pv5+0lQ{VdQMnq0msEkbe!Lp?3MVhR-?sBev>5aS?JD@Jn`0Nlfq*5Yoa9|1SA7 zIgaKvdWz+&yi7U6cFiT4XrW9xH>5_2q5D+!ytuuyIUAPX)o`3A5ZYGhjh9xST3mWk;$Y+Ak{E+1yl@ZIOgncs5CUF?=FBO z*=-VQCoL6fXA&GWPr4Wrd{6b$dP{JoY4Dx9AZf^_by*P3H-uq2=Bg|rs?}9K+AH|n zR3SSQgo1T1z<%8g()4xJ(16j~!0tJD){2eN^^3YZ0~BeRild{}up|hIu-bD#4=Ntz7ty^RqIwSf zrIAp19@0qxz3&0gl><>m`P|zN%fmUp_H(&9iv+frAT^T30pVJH;Zdk0OTXv@-*9M5 z*n?@JC(Q*g|1Sx9n@N*jIMy$mtu495INtI+spOpGCOvdCHuc}#&)lBS9EFVhTY za?DUV2mQ?Q`aPV1k^`9$;>#9OANnUqkond7q!^i{b?#@3MrYc^g}pn7$K54GIDWb( z1=4o8%6@69P`{clK3E?dl+I= z)H%>{IE8aBxW_54-wZAkm(#}q;vNW7PI&WS!ZSi@YaG~PD$mvhy-jDUJG6) zpSy68L)MO1Kq1#+Vw*33n>k1`bd;Ie4EQNri=@A50E0W0^p+iw9JB&Q=hd=Ny zyfaeF?3~#l2OIJa)@;e@(ejP+&k)Kgftr=b!@NRs;IaRZV&1`fHNQw*rixClP?;gWoF4>gfoiFJTuPe3%et{cO<1|G=PLq7%BRn zGW$iy#EjHSrQ63yuHF1c#k}K#CuAZwXT9B^{rpm@} z7V1{yDvld zAz!5SQq4_38BcpHMjlNxuT(IvbcMy12jmaufO$nL_MC}83WXBm_G+DgI;wzzgn)wG z#jIO-RY{jXjivnm8XgI#|Jzn2aaA{p&zFbA-aW0Yx-6Tv|Fmn?P+*Q=@F2ldG%&g} ze1gT|VrsTW8rZKI=&njgkd0NNjjf~Ar<_fyicR6ZB%_B}=2yVGQXngra*^eH+ba^Q z%6j{wrr-X}Hj2$oM@{5Z*?KxCGe8s8ziCgjjv>BO^(sfrqDif+Hdw1Pf(zt$6#@$& z8S`z8&a1huXcfZe--nQmRkUIbvfS`-3YV>nqHXD7ZD$oZ36~);7MZ`CYPrp-6t7B* zxf;F38uGPjf-Bp~0?P6^$bc?2@|?`|_SB=zS0G-pmf(|sS zowUfTpQQ@HG2Lg2tWWN!;=q(kiw{Uy_C60Hu~qFqiHm(KUWwrVt;g518UP(#`o^>e zTNV56!i!jJfQGt*t%rkV3EkWY#Eu^dogB$P?D7zK=g?ptkQ@%Ab;Xhgk$BK_ejgno zzaC%}YvZX(*CF&piVlTa4xI<~K|2Q{T$_aw`Vz&5?l#m7x8@%6u4nr$J&_maoRJBHOPyR#E2?8STR z;6-ck9URC}QJumkuFd%eO)Qp~{dwRg_o||mpw2W4 z?B?~vGEL6{d}1eIOwG0LaaAi%RsEU^@DWX8Z0A_ccy9uC_tkM1jC*v$1>;dw<(4q2 zPBT{3Ic}lpsa>h(uF=>_4tT&_gk<-(0(~38vWL2YN!P6tIGb8XRTcxSCmGSt5 z0v($^Sne3VTi+w@d86^E_i&(~6v;V9w~hyi&h!AA^1_%&KVGX1EDm5v2I`0)c^}WE{zNH0kL3BjdQNXR&dX((6s1P;R2L!wpKGZ%Nd{G8FkA_ zy{_?lR>N08OV&a2^3zkVt49o0XC5h&oV#Mt*EOoubLBU4#ky@2t8-6jm&>cB{So!? z$3wc~J(xh^R0LwIJ$NagYS6ChD~4;xRB6Sos_A@uWvpv?$a1h3U{uWcr*1Xd4%#iu^-49TN$rNJ|8%Iks#z%ykM z_r!VkU=n#7{M(71^x(mBE1;Y5I%_hBTyhB9`Srui(sdW<-{S>7{tfUq!uib-5dmy* z+b~HOrAiuQ)?Z@G-=NOlpm5(1S;PIH-RQ7d7jUn;AA(~+uiKtG(M64nRx2mvee zz7j**2+-r;ucM2%K54_Y ziip!WU6RosgiLNa=5GaebxBR`p1buq>hC^E+VGhquqtkkCH4raZh_qEe3aVuCboB1 zx1($J_qz6V|Lw=R@3?V;=yP=L4cDN_l;g}8%zTb#GDAC0el`{!~E%2bl+dE z4VfhG{FMA|qWb+u{`k+8p#tUaw&?GVLck3A-~9DQg@VANu$6S$Z|)~u09E3tjsbAn zdPWd^RNQ%VU;p&n<&;fwli|&2F&%)DexLEhk7E684nDGr{OLcElPUS*Cezy{2EPby zzkcU`m;U^V!{C=nErG=YjCbFfr`!0Ou={lV)UD=(CFv(^?GL5Nb*OtC^ZG)&e%o3H z0G;@A_Sz%Nw{&h=O z*;YQ1x;MnZw{Q32HyiPE-+KL3&vq2w`G=4nY&>LE)i{OZZwC3eZMW|qKV0zFTrf*c zTDxDE=I`W(4(ht^`FuXIqCbb!;y98nSwH{afPQORJCUNhIB?&n?>UY7PW)Z3fAog$ zNBf7mlaL=&d{^NG8-JCr1r4qN5J20%t&V)qH_027$N$;RE0vq2&o{Hgi?6kWm4cmD zbiWB7{y?TSifXD%(C11?|3-TLjq#ng<)0lsCIPCpDWH$sIsw!G3V99=R%+G=c>uL- z1T{5F1S^a~!6YeygBlQ?lg42J0GLDoSXfz2%9F}X%H+#gK37Fol~vWD3Mwn=s~b~e zG73#Hs0EWF^O0Zr@)U9e0UUfLf`TSr0fN*D3PWGyS!>a#?X3-#rX7=wD*49p=FWrT z(}Jj?k(h%1_Pi88c$m4=XsR?%94k%55DoKv9l=jQ{0{|8>0IcE)kc2yg`17>I26ZN z;nveA02Kong=)QdQkK-)QMgt^sa`gV4d=3NicMWCx8X~Y3y*E`wB_jMW9%3;S!&>O zNI<0Hd>0t5up6LZU@}mFf|y6e(ulVDZN7{OWZAXCdpI2-BKD5pQSd+L`Y{&!ceg)N z+z+W2GStAPE62U3LK|YW3gnHOnZ55A-y1`7mo(_od+u0e2FRxx-C|bO$QKpg@vWL^ zHmp#udv0uQOEzEqgfi;+bhg_{QJ-Vb8SUAjGqydr$SbuD?KTS1lr{>xTs@qxEfDph zx;i^r>j2~sGRr&(F#AR|C7@w$Q$b`3xym2CWNEk(U#gP_|%G9PeYrO|N zEP)AtbDNO?vn;KQ$}qXqr{bkuOrwJApB!zxbEOt-^>3+sxfPSER1eSw@%Hm-+p0cW zg1xaCunQ@w8h$A%xROnHB+{KO023hdw~gW#RkM4@7M-0ICj6jnDfka2h70;ysGj+m zq)^|cqON&8f1071}t^Gc{$gz zP)MY~(zaK)mZm}QPTFMwmN`_XE{oVDZ7*KjoNF^B7%E;vT*pKV0V9M{+^e{A#a%P0rc<#wwQg6kB~)?iM0#+-N$>a6M40QZL60KR)3vbC zpr6S?7RT==pMMDQ43{Sl-e!t<%&>uHkv8_wCqDJ$*%;FPhw_R;x?XEsn_S5GKe6DD{lDKGIGa-al)|zTctEM81uzz z-Ynuoe(q$DSN<5`p!9bU_919|NOIjw8461&-xtA#RgfM6<2;Sm}HK(YNQSsmk`sWhvWns2k#j3TJ)&>?jiZgqmu~ zYqqc8>?b+q;mL$9)dlf<__1K*G}2=_>6$G;}|ZN=oRA^k7ze(1aF zh40+9Di2G=eTD1HSi&e&<=d-cvP7up`F1-9&ti!3474IWl%}41jG?X$w zrWAMz!+1&#KVe&mcN$auwQUZXB*mh^OM-nq013d+7ZYjb0idaJy8 z(6Yk+b*ih57722cp%>A8sYPXJ>g`}O<1}B1*HtmDt{|Jx`1**jFc| zVRA=+r*cMlZJrfk45jAOqC8J>YHOY6Ikwbpm4E(`QDgMT)!>hUsrwmAQo!Jmekdod zh>j&zBjf13gw_yQVxPZgyNm|8nn9NW!dO?-7Z&oB2RoPwq|=!(f&Snm?aMQ_mon>{dmtS#;EELJwhLK zkk0J$uD*fns-8*+)@r(HnCH;C3@xH1f@|$06yq<9C{EF$Ck^H!8jdPx7mHl+; z#fU-1gDoGtozviA?I%g;1X&_KUz9s?+`*`U>o=7V`!D&Kq|9G$mkDGUDz-C)tq;Gf z8aS!{HJW?v_JBVRK3!#P_LcrUx2`&^IZLf%Xwgrb9WYe zv-2{K0QmkF^QSAUlIp59WJ0>*M9H;pG;hwv$hWkF2O2;2q_W_4#~;?drGG)yn*xtblK#HBXU$A=@vo6)luC9`!=pn~>U$wI*7l z_8{V?&AL?*Dv4tMB?($Gnqb4V20xqgo{M z!a!dD!X%BuoCtP1E$=Sw2JAM3@-4dYvWM?_I7c>Bc&q*mvKW(3x0T+$MHlTNW zp{EN^AEH8)8-c9s{*ze||FEI|{WDOIi{#uVW5_0B)+Be^k>jHX4^T&*CD43WZVVeWyx?BY;QH3o^v{sH6?^0tlZbl`QIzLF7UvNUT7$&dp_91i z|4>{qcIZ9R7}DX;u>B}o_6V2Q=wRRIJ_>oCl?6hok)Bn&sk#T46|vq-_+e(=>5sIB1%}?U{*JpYZ3Oi*YBY zgbdBNLsXo*0lBWXlQ%MQcZ|MTZ4UegWFG-WDYz6CGp}7H@;4)?JaK{E9tmOx z$wp2p@+0Bi5PyAtn7Cv5Gp$VZkxZ!(7p9R++Jh_u4)PGnOlkQHo2ASc$}9nnETz`W zgq-ZV7ij>BOkb@`agn42hipH}bWw_Qg}6vj(^NoqR?bBj7liy>PHr1EmCGzM(n*D0 zBuD=u1t6DIFOumblEJ%_QQMZyPLcUGE>rtl9ztOx6&pF=7qeiJuKOoVq%$S`QjHRZ@$KS=%Lll zWceqY=VTQ*Wt9)V?kvp(#HNWZAzC?7!`cvTZOG0GQhNF9fs3#@t@POov&)0@ukyKS zIRy+HunCc*=Su~nONEC{$R_zhnq1`1I3$A^xset*VI%^K2Kyt!oeTgjB1MvMfffdU z6u%;QZE`TWh)b#{(yZ75T_k&0)h?Cq4N$z!;>}8V~fFysU9mJ_c{z{?5UW?ox zNiI!UB*#(0H&XJFvm_vy%nK4HRZ*-}5&jWUCZkX+s!8sZOAa;wJZcAeLW{iHi$0>u z+{}v~kCw{7P+ZC6d`o3cQshwqrQXhEA=+hr+Qrg`#SXbeO0gxrxkR!^j?(+>rAFpO zAMnK=D#Gb=$n*m$`~gKaqUG_RhoJP|d|(oNW#$R3MLOFLDtrZO|^3 zg;gq=lY?z)WVFj3iPWl(mWj541JD&NqZR4)nI9pgaw8QV4XP|JQ}~u@Z8*_j#S}iR z5~t*v4=}XuVk*|Du3)}~Bd>U-tj-NkifV-M*Gr<=e0%zRi|T*cb?Sk2^wbUF z7S)d}QZA_)o>nw!1vJSz*IO);2`tym$Tmuj)!Sb+)KV3xQWrUom3|njFT1RBz}K^M zR5()?wZ$~PJgSvwYk4FV<_W0a(rK``sux*qaj>W}&TEJcguA25lr7S1sKfL|QA}5* zE>tC;BeJYKa!^NwMMcXi7}{044z186W>H5`*(iS0V186@8{dSzXqv$lSyQ7e&>i+| z9Tv!rp5=~C#j^gR4h&c45EuCysm=k5&he|x{*KPx<<35?&e<#QXkO<~W!H3k*Vn7A z^$swlRM(VZ_xx4YEOjR~u=_Z#6Sv$wuG6`eN9-Kv=q7Y@{5tB~?g0Oy?!4&eIn(Lc ziSOK9?w~|=jK+7Z<#l6^I_TgSI__S^@t*mk4sbGn@-!thx0f%jmpiCeE0-(~+DmcN z%je(AmjIHJYITD4KE?ORCHHP}^@Vcw$!hoMjP~ll&}`Rzn^%3j<9(9XDVvr35^#W8 zXBnS--;aPkP2B;u&H*;1R?pM^&(3|H+sgty2Jfs4NK_4&T=!{=_o-XQ z(rcf9=BnzYYAYQ2($~~gs zIw&jJ{n~QmiS9Vhb-yRN_Xobn&T>4EvoCP8wC+{j2U%?`D zN35?WKUj`wT20GXPBAD?5Lf2(6Gzgn=VRcbvM`V@y37}X4KxRcD&fFVI5`iTFA^7s z$9V;lhwCkdci}!+O?oRYMg%WH^>Dr?xKQQANCY7C1{c+Z%T!*E*1;gWdp0TMW$ z)ujZU#ezg!%*i6u3Kwv*SV6m(8H^LvU5@Em%0IycuHsYl7OQC&n-cNiJpHvi{f){? z6|1=NuI0uq9LfzB8;rxa;xOF!lrDT#^>T{xa&F>kzA}EedR5eN5ly?=af6rSUR&l8}`|t938h)#K{* zm_!^_89(iY-{V2(B$pAeM!baKl!(MP+*^ z-4_1cDgNMQKITn++Gl>|7@pTJuIHMq+oO%)=^fo@&gX#M6WhPn9b>(j@h0b>ZP9PqfY9ep4Xwy>6eb`u&(N{KI@zQ>Z1PY zu+HkV?&`S?>Xd%!zb@+^UhBk8>K{Jd$e!%VzU+7~-fNKT&5rERjst682hnZ^<824n zp6$=B!rH#<)E@3CT<$0!*sk+i-}dQ_^)w&vP5#CufmE?_>Hf^d=U9)aQHY7`Db7G$d37ZVEM}K`JF%Bm=E}9@bI4R`CNbc zhp+mE-}<6&`Js>agb(|XzXp>J__&|@r*HVC@A{Gt`=C$zh@b9n@CNBl{Kvlr#-IG9 zfBef&{Cwd2&#(N?ulUZN{MeuU+Q0qW-~Guy{l)+N;_v*;kNnbq{=^UdyIt(BDdNqhiXO zNwcQSn>cgo+?i7i&!0ep3LQGiXHlX^lPdjbbg9#)P@_tnO0}xht5~yY-O9DA|JScz z!-^eCwyfE+Ue%~w%eJlCw{YXiolCc_-Me`6>fOt?uiw9b0}CEZxUk{Fh!ZPb%(!vj zGms-oo=my2<;$2eYu?Pcv**vCLyI0wy0q!js8g$6&APSg*RW&Do=v;9?c2C>>)y?~ zx9{Jj*$5v_ytwh>$dfBy&b+zv=g^}|pH98H_3PNPYv0bjyZ7O6!iyhIzP$PK=+moT z&%V9;_weJ(pHIKO{rmXyv+u*dzyJRL1}NZw1QuxEfe0q3;DQV`=-`78MkwKg6jo^A zg&1b2;f5S`=;4PThA85QB$jC6i72M1;)*P`=pu!F#37|%vs_CYjcIxS;poS{ysHB!^>Zz!vs_Lq&9x2DGV7@A=m$JgzWvz11TC1+J z-ny%=y>f{wueJ*NtFgguIV`ZnDtoN6$!fVQvdv2SthLc*IW4x?UWse2)K-h-x6F#` z?Y7#YJ1(w7wCnD>@Ww0ey!6&<@4fiutM9)2_UrGz00%7a!1oeh|M0;GC#>+o3^(lX z!w^R-@x&BYZ1KeyXRPtY9Cz&T#~_C+^2j8YZ1Tw{r`+)$EVu0P%P_|*^UO5YZ1c@H z=dAP2JooJL&p-z)^w2~XZS>JdC$03-OgHWH(@;k(_0&{XZS~byXRY*wdY`5+9+i=G%_uO>XZTH=H=dJhNeE045-+%`$_~3*WZusGdC$9M7 zj5qH1^?ythes^>#)Zz`|PyW zZu{-H=dSziy!Y<=@4yEy{P4sVZ~XDdC$IeS%s21+^Uy~x4*m4hS8x6G*k`Z3(18E| EJA#A(uK)l5 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-personal-cr.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-personal-cr.gif deleted file mode 100644 index 494adb832758f271496533b29319d348bd4e55a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46703 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
>U|H@NhrFpa59XG+tW+VrM4&8bdz%F~|u z^rt`#s!)eY)S?>os7Ot!QkTlqraJYhP>rfor%KhTTJ@?}&8k+T`iHK1^{ZeFt60ZM z*0P%QtY}TETGz_fwz~DLaE+^6=StVQ+V!q@&8uGb%GbX7^{;>ptY8OA*uon2u!v2p zVi(KU#ya+~kd3TlCrjDNTK2M-&8%iO%h}F)_OqZ3t!PI}+R~c#w5Uz3YFEt5+Sa=E zwXlt?Y-dZ`+S>NExXrC@cgx$}`u4ZL4X$v9OWfib_qfPSu5y>l+~zv>xzLTSbf-(* z>RR`@*v+nXx69q`diT5F4X=2|OWyLD_q^y$uX@+Z-uAlpz3`2%eCJEw`r7xt_|30= z_sie@`uD#84zPd+OyB|=_`nEGu!0xN;08PR!4QtHgeOel3S0QX7|yVUH_YJ<{9 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-simple-cl.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-simple-cl.gif deleted file mode 100644 index d7012c94b5196dc08caf5818ff7d34a45fc83899..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17705 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-simple-cr.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/bg-simple-cr.gif deleted file mode 100644 index 1b069f2cc1b0c0c6961156222cff6011c2b9ec96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42137 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-blog.png b/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-blog.png deleted file mode 100644 index 4e2fbceba879a0ef6d28691e0a199643d83a7259..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22650 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-buisness-repeat.png b/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-buisness-repeat.png deleted file mode 100644 index 3f67905e55ebe87f8c55791acfae029489c7fabb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16942 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-buisness.png b/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-buisness.png deleted file mode 100644 index 3a1ed20a9777ece71f3bf3b8cc0f00299af1133c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26481 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

g2E>a654JG?4w^?XDMRSMQ9;@m=*rkl|Ww?kd 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-confirm.png b/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-confirm.png deleted file mode 100644 index a21c3c0198c3e1519caf1c6dd9bf55f972726ce3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3113 zcmV+^4A%3BP)Gf1h#;T zL`jmkN|IQ(C8i3A+!DDZa^aT9C21|ubkS`r_jVmc-?rD{-k;^pbEdts;Oa7mNg-FOimH9uUi5Aq}inBpqC`%CUQ4_jgFV@hI8-MnD?9az&p?tPlN3B zKz7(5JNw{j`Y-gS&q3F^Ww35|GU0t2V)P zs2bYBiO`qNgXW~uka6+tXsP)y=KWFvX`hWZj}4UPEcpv7b9Wp``spNLB$^jJ#t3vB z*@wdw(<07iU2+G~O3#b=SPavD$Z|*B=OJyktX!O0?sWgvMr*vSu;__BkR>k;T$eSh zI{6jQ!r?o+uQ_r~)mg|7xQzsizZdFWn2<$7>iS z=f$1yC$i0Rp^ zy`Jr_;~2Ra=vWP9bJ!H&>1aaJ@8$)@87~>Px;L@zG-KWpc7c6+HV><>q7`32J7zq* zJsr$~G?pTEeDb3^aPZ!#OhIDyXr@fl*;lZegSPF*QR2EkS`A4K?MsHTfTLp&)6J|t zZHFEE+A|Q>PTTa)+4P)xbTK=Zd-E#dNowJ)+W}+QTt>XUWF}NudFcI$%)12>p&m5` z`S-3z!#$Hz%bjXZ8qe~+Js%9&mwCb6j0jKrVbt9+E>K=@{~Iu#F%P*nJc+}9TtJ9Z z2%&d=+Gi%9su7 z)-`Bb_fVisztHmLXMU=9id+ANjG0#vE7zm`j!6ORX{&q4^`4f{c~m?5Wvx6Dr_+mrSI@^|8dWko6z*sqL4QI zLdygFH0)+&_OEw@U?sbiS#i8|dcrpQvMdeRmp1=9Ebsi_m56g*vYJ#8H>fDRzny{F z^!F7jTzL6V_(WP_LP&HRszLhX`7lg8AJY2DA()P$grIZ&=M#QQ>Q}Dc!eWCPY)pgV z<;0WdbEol5t^a<8b(UT}dq}LiyM%3Y+$(y?~6E3>pPq4d}aM4 zgtir4{!MX05aH44N5jTG4d712xSRZe%AcTg<(Eh+D8cEkwxa#Lm*MU_3jNqg$eeZ& zR3dLd3-x&gMY-e(x|`ByWI9eVZ`Y`py!B7}`!2q-Z&xM1rN zLr98-kH1c(#FOY~Zor-;lTm!%%P@|gj@%#pGQ_7j4(&zrx)p3J6j%DxHbUflmKcO= zuInHF8M>>sG6&*UqT&w40p_Yua*h`lG|df2Vnn+MBQflW&8PFztVporU#9%6vb((* zZJYl-@SBdZ^N>D%T2#!pRc35-1fJM161UIOTe(`2OU~M44}a_?a;+r$Ta5744wob( zky|3SL@wMCxo}J5!bwbGk%LK;y^_!&3NUlMB!{>pnf{%L^IUWRn;XQHl|`eED)?Pft&!*XunZ za~ZMXcDs)@H8njwW5$dE!dtq3+G7hFo6S~fG#YtCsU(rw+S(UNN=gC|r6`G7!tfDM z6i0FOB*|vq$}Wz7f3MsMp=poO=a@;5uYD$^mZ_$Wj*i59 zi_z5t)rLnP)NKp>N>iXw$j3su_^YT-~YI)ePOA7n0sou-`Yl0DRhYcS2RagDBXegcLCs!F~A?kg}A2 zprfjO8=7|>hg5tvVDAoKnZ?b$3*LzrGK}`EN7&6nU8$@yu)2;rHbML5GKh7bgkb&c zdCC_+^L8bKz7B@fy?7&p&O;1KF#-*9LfZ3|&VlO(%apMm*v_U1I>)=NeJ(K0cu8Jf zUSi#;`t!By0==h9A68%b=dXs?evlC&c4e45ZP z`Eed%(R~;FX27|Ul?U}h-vfE`CxrAbYmMYau%;e~;UT!9&Yzu!S?(M^Cqjv`&J0G;B(c=*cdE=HO>gH+}(W$`!+^6Kzbv0renbz=%%af%lFZHyDXS z;8TvozOn_-fAJbLhlbulNQNw^h_$H*;qHR+(7DysOH5UzDBbT4!`x(e`8UP+%Y<+e zqqHmF1cU{nE+L-DYZ?uA-UM=GUSz*=!+;q5&Naw<<9@hvlmLB(U27uF)B2vwF@dsR z*A}m`|F?WFY^>7>+3a`C{S_>0Zi2eA5tda8VK13Wtj-Kgdp)!bpFwom;5>Ocx@K1( zuWl-k&O=Oa!LB!2{b<;fd151_=c4tB=aBX8 zBha?&gRyFT$TJ5|b|GwMT*k&iahBI_gRXH0ON!4>q4WH^;21s0w?&WwT9m>g?hUZ& zJZ{{e3!CWpM3Ts!*wob2Bs>_Asp=ofQr&1q-?$3~#ORLfhPJgfD(2fNrKP2zPi#)a zt0-&P!U(Gf7f1dimZD*}@R!1=Ln8n43~q^BxFvGomdJ%$A}5i{iIlL2QeJO|D2hCy zRFa6x<%*0b)!yE|)?%^nh*C)+)z#IJ5v8iCs&-A8G9^#1*B26t)N?hBOtIVT)|#4{ zEfp0NtDH_Jk0_O-b0Ci>l_Es)h*BwHjUf@G{wKfyQ{vqJlrJ*c00000NkvXXu0mjf D7(^Eb diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-create-hover.png b/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-create-hover.png deleted file mode 100644 index c2ce62970836524c1727e273fd5b04194f8d4f64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2243 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 R<5~a!002ovPDHLkV1kvJUR?kH diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-no-thanks.png b/src/Umbraco.Web.UI/umbraco_client/Installer/images/btn-no-thanks.png deleted file mode 100644 index 810f857f0afcb96b03a77ef92f333039e49fb00a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25962 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 z 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

lrbgAd-LHgy_rG&CBzZ!g;&E+M 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/img01.jpg b/src/Umbraco.Web.UI/umbraco_client/Installer/images/img01.jpg deleted file mode 100644 index b86b30f7ad6b5db4c958c208e47ea22a617a9475..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3189 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/img02.jpg b/src/Umbraco.Web.UI/umbraco_client/Installer/images/img02.jpg deleted file mode 100644 index 0e5669be88083a502161ac12729bf43714737cea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3533 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/img03.jpg b/src/Umbraco.Web.UI/umbraco_client/Installer/images/img03.jpg deleted file mode 100644 index bd0aee6e12ced8018cd95be8c1fa9e92468c06c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6205 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/img06.jpg b/src/Umbraco.Web.UI/umbraco_client/Installer/images/img06.jpg deleted file mode 100644 index 67d09a08ce46243d7a2b36999171e8cc0dbe4b99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4408 zcma)8cTm&K7X2k5l+XnvKoAf^mEHxV7wI4!L{pc zF(3lctDwTeZ{~e}zIkV7XYW0C@7cR|W@q+d`eF&7(Nw#u27tf-V1GG)i%o!5#m~+) z2mk>@0078cQriHPqP-`|0kChsWIz{lKomesL_`E7B8EbV$%ro}IVmwQDLDlUMh=5f z&`|sX8VV|E8fq#EIy!oKIywXk3k!nnUw}wRNGK^N;WRXGW(HaY=6?hKuW`{1(2@cV zffxvg768+NAhe*1ZlDYRzz`4!bXn(r0Re%Dpu{AXX{blY2iB?!nmy1@G@lXz?1sKe7G{O1Y( z^f~@j)~((8Ou1T-8T|+rys?nS*iegPnzdfWseIa#-Da)d1!ZApIpN&C%}<&e-aPgv z#?P>EE_temV`knudaI|4I&`CxZ+(y-_?>5Znk9Jta{ch=9QmbxLGVS)db4xmysT-8 zjJK0lp9MxkT`Q<|JN5v!asi0#yEkWC0M6l_4ew{3{LcSW*6xpo#0_a)VF;|)5X#AE zzHcs-OsM?@{qzkky*LJ{hAZ4ifd}R z>weVn$)n$2W?iP6?TZFzm4;~hr`(pSY#rO^P0MJ$uS~G34O;)^u$^P_k2)J2Yw;&N zej(rH>J|I#9Q~iUTh|^1nFp6lrf^)TfxKNi0jb!F_9bW`2Z z=>?E89Lf8V*L30hnVx5N)B&$4r^)!|2d@i1jSrcc9*f*LhHvhyP`ZzV+0l`wIXDky zwYxK{^&cE4vwqerbNL8I%H`2}y0j7A8B4UK5%{aP22E-jVmW;zpD4SrFD9At!@w3KdP@IiG5uhYgO(3WZq56B$8mq(sixh zLW}%X#}Hui4k59&FDhlSVA|B>?^ks{-2A{<0)^Pr9q+Zm5`3J_h@+PbD$KiX3N{x! z?3}^d3%J(Q^(T1qz7pbm4W501KF7u-p$vkV@@B46+c^8YY3CuY$EIH`d|o&HZMbQI zTvmx)$-CU`+l!Ux(n|RkFG9DM9%wa&ZM!A8CbFfdwo^zlW79`062qp4a2|C~S>hiq zP(nqM0JOB^{d#qiD7kkiZ}be3!Up+D3Ee_Ltuis69XTO&T>qWd|GR;L!kr3)(x&P^ z2_2_G+P{r^r^_09O68I3&l4Jd(a)nM*wPO_^;m@_c!;=A_dj|_cy*^o3SLLDGAy=d z%7(%wS)}NAo`DGDxz`JLUijVv`wa4w@Eli>St z6`u(uVQst zUO`TJ3E^AMUp>c7eO&Qz ztjE-^3ni>-F8}lE_FU^N4=qI(>e(vOotf>0_3_JG{jf=wFJWK&cXa>C z$NwptY&&gRU(A<6;_6s=ezPr?7TbIO2)LOn%=|&(fVd9F4v={P_xzQ*E%%?Nygz9O z|0I9p+`@qZeU)c>^7Lr>0=N>sF@71R+I^ieS@GH$<#m*?$97g{Vt%zd^aC6<-#fx; z(9v(5-S`YE_C;Kzi_@N{!OfeAWH|CO@3LnGt`xUqHl|Y81>Wj_;b=!!UZHi6Cg*W= zjAHxUh^%IwqlVv>QbuTuFMlA5Daz67Js6(Fpy`6bT+rCLR{)Bbnn+FW(9*wTpocWI~XTP^K*otHy zAy;H~zusKBi@ZDEGwms10gG7`>MVe5@+cDU;TuJ&&BO#@aC2i3X90l9{limF_s z9a=7OGqgOC`5x;kLc&l$cjej#`SLpn>{OV5}d>O z(RQJ16mZPn7nvw*=EbGIvL|0>#(U>Kz6JxQVfPPM@Qtu4>V8^}Yv>dt;3@Q&!Q<`_ z*rUjZO^6bN{JiCQ79hx8OO$Y37yd%-kZ85lC-TH5c5IVus-{C?0>1?2 zk9XWzeMWg!zqJbvSIFCWwqY$LuwC8=H+9up$`7bp@dMxP6vwr9^y7{L- zUvzveEUpA|Ip{WAlVB<06>JzERPf6i5Oz`=uCH!9L`6qQ3dmTxc(t=H880u>zfa+U zsjP7Xrr*BfC>N#A@@9Ef7q+IX_%0A`si9fusp>Dp3f$ROE3z2+21Caz8PhB)NRlND z35qm}^Jc9zh8k{wGNH)4G%?vp24V7gjA^&P1(0~EVTG%MH7YLQDyycKsx{mZh ziilJZv*S)kTW)sJ^RyC{Z^ws1U9vjpsY(+-E3HEJS{?I}RAPM3z`nw7p z{`)~rq+s@bxQcXY8BsT*@KUM%?k0|daDy_~*2GtXMpwz3!ih|zqtz^rtUuAh7Ax#| ziw_1KQ78K3CDJq5zHRS9gEf7MiR|l?7>=rrY9Mu-o%iC85hoJm@p##8*>;xm|IqIj?GI)T<#^x3a2;|Di$;@Q%fnr(JjHIk51aEGWCCuyP_a}DIRDe;` zka%lN_K#Y>?*`f=KZwQS=$ggU6Zd(PJ^gUTJ<+0r;DsIdM7mjhqOmEu=_ z6vpW&?XGQtiEw>HMG?kFCZ&5rko;`|CTI4G6o0Ynvn8Xt6CsEP1Qds3pIL+t-mBvTtJ;iXmeJ5n+==LPZ z##+!?)7_8;Y=W7A;=SHAy$TYvLYzpWOaUj@7m1G%%wx;B^dPVD$1<&-t^~JndsbWk z4la1yU~qtF;GpeIrpVNZb~dD1hm`+zIzeeoYgRMTGS>}}sVyeEd1jEj^PT(NPd%iw zaN6wS>E0vtQL~U?(T{elQ%-0%eM%+TmXtS(whAnbljyp@5&f$;7=VL8@(3OWrvZlB zV{PQgJ6x+y5uzgfkDo@N__IS=ynIUBjrs2A0DCc8g|kPeUfr#~jx#!=$PC-~es6>y zq_yb2j8;#OmstPs-MqD6T!hY2P6HLLxPU%sZV3q=tZbscng059AwsXxbVm64v3f6+ zBjVK%IQfBq5mRF8{m|v6*s}^Fq?WRemt)c`B}FeH_USgwC$J1}Z?BmZS8HWO1puL( z9#7oew(&ze?9EYQto^=8I$6HP?WAy{P=?r{aXLSrX7o*4r3Sf(XRfuU{F(EuGz^RV zpkZI#F8uIX`_>O32A|B8rtwlTA+)euGaG@}W<*2>KY1_1_^x~@$LDq$i=T*b49gOg zdlcW^q{kz6t51R^X)VLaj-E0~sG>Lf@*X9u9w!=O)*}-u2Yj-R3G~eT-76S9joZQX=@Wp;nqWB`FW- zNg~P9D3Ydn4_mZM$9L))qbYX_`iAZkTe!Q$80feJmk#PGeqxl)K3dv`lb(x&$xl6F ze2DEaf-$%36MpG$ik^)pb9fEL&_kHYn%Xfx+-7R(kla_ufWsgja?6d>M*M+7FMS^q zm&5Wb6x58-nU3pBZ>%mCfoGkrMWh7So$Q=HICV9)7ofY@P$`htGZUk)1P&_QAj{8o ze3&z^`mP{DK_5n?nSj$;t{!H46>jS&5BOK_6D77rWOV zdP7YqF%ukyo*KtA&p@nVQ(q4PrFmeh6KTQknBH%*sbNxo9C=IbMyqgqXA@GxMEX&z zNG^igbxj#l@Aao+#9ZWKF&BW_nK)6?Y_>r*saCadkglF2Cd4Ki!pZ!3HzMx4qJkxV zV5|f{EbQh)n3mM1Q*fAwzd8U;U3#42Zp8G>oFJ2w$NItwP@^Wb%mWSFRRD&f_DzPc zzM&?T!^*xtWZ3N;@S5ip$@T-n@CgTHzg9IZ*+_G@UrE~sRGpP%Z+4}hr^kt9UjF$s zZS22LHd4W~RUvOztcpFLkLF`)BX9dk3AkyyPVXLLShG@myjYZ4JE_RM4~ z++7tXEN diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/img07.jpg b/src/Umbraco.Web.UI/umbraco_client/Installer/images/img07.jpg deleted file mode 100644 index fc6e89a460ea93aede6e2635b078e4762becbf9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5924 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 Q>Dv=y*%C*t3~uKC3j#9a4*&oF diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/img08.jpg b/src/Umbraco.Web.UI/umbraco_client/Installer/images/img08.jpg deleted file mode 100644 index 10916e9e685daa24ebae4dc63858e7eacc48d802..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4535 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/img09.jpg b/src/Umbraco.Web.UI/umbraco_client/Installer/images/img09.jpg deleted file mode 100644 index 723dbbde621add4c0ba030bc635cad558627c20e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16018 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 J^Z9K0e*otf71#g( diff --git a/src/Umbraco.Web.UI/umbraco_client/Installer/images/loader.gif b/src/Umbraco.Web.UI/umbraco_client/Installer/images/loader.gif deleted file mode 100644 index ccc9fd6b6fdfbc10248b51f5702ac8be8028a878..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2113 zcmV-H2)_46Nk%w1VRHc(0OkMyxnN6?Pd$)OJg!_vo>oGwJ|U!7LzzA#lu7HXE^AN32{&o>fAtT11&tK$1{CpjASaQ9qPWJ(^QLl~O*o zUP-fDN19YWxnD}MUPzKoI-^)a|JI!E#fxR6(#?N4Qo#$-JtkSw+87Hs`~UyJ1VSUPj5ZpR-*^tyx5?T1Aa9 z7Oq}MoK--PG8daQ8t<)Z|Fv`fty1rtO7EOP|GjhXtz-YCO8@`=A^8LV00000EC2ui z0CNEt000O7fCV8k2wY?&0RbpvTnGUxWMohQDLY~)i6~-?j}~H+2s;)E4*?fsT`8Rw zLIDUwqX7~m5nYZe5f)UgunLJ(5oD@Cxk3^uJ0e|~A{MGCxfcN{TxLYFLKYE|DP$su zVAVtq64? z58{{vBY=Pl5h)}<5z&H#1TZ^VkRj)!g$pARf{4iAWFi?a793%L;pItyBX7JYkRZWI z3kG2SW_j^aFUSV~*T7VOA;Mo3vQH*51@hp86#xTL2#}J%%LOn#CIGM^z%YO}KEN<$ zg9Y*jHgOPuS-^&aj}v78Bp|WG$N&Io1b|V(#)}RKWfA}&u;hdYHVsZlKyjvk91lX! ztf|4gd2=Q-qD!Any?S&l&a-RZUIV)I@7A$%AD`Vj`19Avt7mQ=J$Ui!$F~QcE@hk z!bzl@bmnR2i+JWa=bjX5`Q@O57P_Sq6}Sl~ihcUo=AtkSDk^_HDDY^fMQ(a3s!OWMW~QmSiYcqD%6cZMpN^{Qu7pZ~z^k-QXsN2ORygdY z#nx);o55nZESbgT%Imb$>R7=8bv9e&v&=So?4`*H%Wb&MKAWtx)uyZNe*^r0DIV~~ zEAPDY!uzeHHq@)HzV|ws@4f)X+pnMh1`P1PdK$bhy6ZOV@E;EBaHhfsk4y3Yz6eix zF~1gXOmD`2dOR<~50^}>2@=04^1L7`y0W}2Kl-x39nT!|t24t(vdKJ;YQhnE+>po6 zL>F!J(L>*$$I?tU?ex=23yt*DNGlEX)lf@K_0~ya?RC>yb4|6?Uwa+4*j4Y`^V)@4 zfxy{l8%=iESHm4Q(R0)NwB2{-k+9U`s%Fv(Yf9?v@W~suhSj-?6{|1d)c?=j{ELk z_wGCGOp8wX@dXl)#PGyN?zHgCuMWJ~(Kqir)6iFs`t(?5ul?M{BQHMxfIkqRz3zLY zum1Y%x1YZE)!*R${PV*fz5Mj&ufF}v>a+1|V9njOttC0@Fx8J)Vz`^YbGL z@t8hA1~GqbeBelFn|t7nS>pXpa(}VzycI7ODF7r2rmdi6?VXbDr_N_tPBGJe8~eL z=%5LvG^P#|umb}eAq-ex00T&%0}?P`1Xn;n4|FNZEi?fLJa~Z)EV%#zu%HJsh{FtY zU<48@paBF}Cl44k!1wYsU1BQTvAuwP9K!5-Ql%Ro;LZG1hG=WJcumKa8KnWBWX*acb1ql3L z0UA((O@W|+1^nQp4A4Lke1K660Ko(hP(VwEpo1weU;sUyHEB+Na8nA5fB-(A!w3ej zR1@$Zpgv$h5n?a_cm8w&S@0)7c>vWJOn?Fe=)n)J3Iw@MU8_|CxaevZ@oIbPqFI(=X2^&g1V2EAXK{C;ir_pRCgw-*23Tm1fL z&-bHKzMq@&|JodeQ7{?;!z%=IK;8l61qP1i4B{Lz8XFp$n>mEFVk#yu9&8uj_n1?` z(Ad;1VVrfRLXh!jzk+kr6OT!Yk5ACxmx$O|lyY*qY~-mqp25q!XQ&EZxfA(W&2O?z z^QJF9gBSZPbnFy6S|Re%kA-uQ*IciySyz_%Wk=oJC7OM0X28y_y}!O@HZf&fUKM+~ z%%Nd>!Q)eNf0r@b-d(}HW9jMX>+bGpxcKg^_l|Y97+D0g{mxWud~|36yF%KX9h)8> zo51a@c3|e`r{{YaJLG(KZOObi#kM@|Y}M9=%Nr86&-0!A`Spz*?$__Vwc4J4??C?V TK3#?%c@Gc!i$9kYV6X-NC#aWq diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/marker.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/marker.gif deleted file mode 100644 index 03c48f4109b7cea9a8ce05b6db776720fe34f89f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61 zcmZ?wbhEHb)L>v`XkcJa$tnN;|G(l-7DfgJMg|=QAOOiQFp0SItvubEFlP>rRQKk4 OHkbdqKDZ&mU=0BF2omrB diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/marker_rtl.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/marker_rtl.gif deleted file mode 100644 index 36ab05d481c0f7d82bdf6b3a8ab415d9f14e26e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62 zcmZ?wbhEHb)L>v`XkcJa$tnN;|G(l-7DfgJMg|=QAOOiQFp2i`uRP7ptIWLRR`=#* O3s3zod~gFOVGRHTl@j;> diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/plus.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/plus.gif deleted file mode 100644 index fcfe0f53fc3d5f44e692e6a71f24f64af700e7ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52 zcmZ?wbhEHb6%&Is E07=ITf&c&j diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/remove.png b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/remove.png deleted file mode 100644 index 083d3f3164e338b0b2842adee70415365ba8ef0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 720 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 diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/f.png b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/f.png deleted file mode 100644 index 832559dcca7220ca7264ea5e7111a564caf76008..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3237 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 XgwVZ$8ZqcX00000NkvXXu0mjf9_l=V diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fminus.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fminus.gif deleted file mode 100644 index 06518742e34e823ba74e45e629a3896c284d7969..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60 zcmZ?wbhEHbWMg1sn8?5o85yajrlzW@`v3ob#h)yU3=GT+It)Mnl4oGzog-_mC_X`~ Mp@^+4pOL{D0FYb^e*gdg diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fminus_rtl.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fminus_rtl.gif deleted file mode 100644 index 3f223fa38a168b8601232f5a5e6b3c8076935ea0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59 zcmZ?wbhEHbWMg1sn8?5o85yajrlzW@`v3ob#h)yU3=GT+It)Mnl4oGzIa8M;vElNa LPNN$OxEQPfo(vB% diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fplus.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fplus.gif deleted file mode 100644 index 39c5b369b832ce464a46bbf4a53190dc4969b8ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108 zcmZ?wbhEHbWMklD*v!E2=g*(tzkk1a_3Guzm(QL(TfThx(xpq6ELk#t{`_gvrcIqX zwWX!y|Ns9CFrfI8g^_`Qhd~D<05XGtMe@Q1OW%cOI9z-MyjU5IOse%XEx24*Dk03s GU=0A((JKJ} diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fplus_rtl.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/fplus_rtl.gif deleted file mode 100644 index a15ed542fd6a61ad729182ed7b8533687cf7c931..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108 zcmZ?wbhEHbWMklD*v!E2=g*(tzkk1a_3Guzm(QL(TfThx(xpq6ELk#t{`_gvrcIqX zwWX!y|Ns9CFrfI8g^_`Qhd~D<05XGtMRI~?rkZxB(!{A8SDhFfrqxI`^SmsL=n-UO Gum%9biYko& diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/lastli.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/lastli.gif deleted file mode 100644 index bfd45a04cbb5027e96648e808a12f89d048f117a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55 zcmZ?wbhEHbF2lFH^( HRt9STW-JaM diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/lastli_rtl.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/lastli_rtl.gif deleted file mode 100644 index c34cdfe874692cd78704896723ba5c33b10fc6b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54 zcmZ?wbhEHbEOD^brBh!<0s&%av GgEatCm<_%F diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/li.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/li.gif deleted file mode 100644 index b0839e18d0a0bc2eb4d896c4457c8578edf7bae8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47 zcmZ?wbhEHbWM^PzXkcKFm6iVg|G(l-7DfgJMg|=QAOOiQFtPRI3#_VOWv~VS@^=YP diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/sprites_ie6.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/sprites_ie6.gif deleted file mode 100644 index 782631ddc6af183f4ccbd2e287d4e57cf3f25833..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7307 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 EKd1buAOHXW diff --git a/src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_background.png b/src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_background.png deleted file mode 100644 index cf55ffdd68ed42f2d70bd7ec2010cee86c110816..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1916 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 P00000NkvXXu0mjfA`AaT diff --git a/src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_hsb_h.png b/src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_hsb_h.png deleted file mode 100644 index a217e9218e6a512b507a35e8a6141f0e56193439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 970 zcmV;*12z1KP)h+v*8l*BQUCyPMF0TmW&i*=2LJ#e`2#0~ zRsaA4P)S5VRCwC#m|aK|K^(yUyEA+4?d5?fXd;=M7I&7CAQ1#X&_j>CL_YN-gn@+F zgHSIeAw>3IK|;|7L=oge5IzJ#A^0E&>G?(8=|!HGo*#L7AKu<>4|8pMd)HkZt+#{w zXC7weW^aBB|Jj-SF*9F$o1tx>E$qcMycycAC@-ZBJ@9=M1Art+Kw)425XHpaLf3nT z*o$qs(c_|+-@n%!JfIWz{MV(NRK`u1oK!3sq$xeUk30*0rTO&r;Lz}xX3Q?2XDLZ^ zNtQLm=b0`ovGcrzGy{tZJ8^M;iCsNLZ*=us&BvV@!eqi&`J(hE1%Vf=JRxLR*23X^ zHtTjcstkxmW4fX%R1Gc%0Gw!YjZb;hvts~YStk9-f%l(m0w0fy0I;#P!Y~ZOFd0Yc z6v|xg8E^fO8d9H}oln`OnfJJ7mbVJzw2DT;EXx*f%c_Y}(B({td#$~-(d7^m2>@v8 zyhBz%)<4CSQ06-3bdbu#v?m=s^TUgQAW80>SVT-DWO+Bqe^ZT5c^X^}GWW!^H|6DM z{2&`t&HcxOX}{XxtgBuQu9KfyE}VDPRTnB@)bzR0U55`X2iGIf82PEXs$7=k;M$J@ zqW{A-4Q++h&2S`|@upE=bVXp?Ee;`O5dSyi! z_6LF|n~ze5J~HYyjR0NfpC{*ZC2Op&)zJu?In|^S_t5Z|X#`9oU=$;ucVssLNp#Z) zm`1=f0;Ul#jllmj0z<=NR2qTWnkv%>>_H8 zN}^l&B27_@0BI^lKr=??RZm8DS1SN?ceNr_7}^MstN?&p_nzJCxJ2r=d!JKa1US_; z?df}&-f3JTfPWoH*RQmY%H4;rv~L($))vJGczsJ=-;&x0XvXM>ju9#M``?gN>ukHI zbY@GK!e7T#7lFRVZvo)JlL2+7ag6|%dL;F~c&F(xt`Sh)F!UJ#{5Lm80~&$3`EU8J sdf~SzMzo^mJOqtv1hQW_1cCoK0IhW09jn*o82|tP07*qoM6N<$g4YtalK=n! diff --git a/src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_hsb_s.png b/src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_hsb_s.png deleted file mode 100644 index 7826b415077be23ed1b1bf05b2da62d4aa5b1c67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1168 zcmV;B1aJF^P)h+v*8l*BQUCyPMF0TmW&i*=2LJ#e`2#0~ zRsaA57D+@wRCwC#nQcf@Q5eUc-Fr6Onu3&AF^rild$UyX1q~x8l(L`*3O@uvFO(o; z_5wxr@*$1tgY{vMl7$6QeMqAy6e1xqkX2R|TUIxmtGu(iIrnyNAC8Ud=Dmt?_1xOS z`EYjbGoJs>o;~L|ziI4tJI8S#VLo05L|S~@48(C$eTIg-ib~hV#^6c%;`soO zlezfi8;3YL0sx8%PiTw9QdL!zm6bIwEpOSPl&u?AQj2Q*-QCgA(fXtl0Cw!$vnn?|%wceFNYM&oFvQ9@3=R%a03eJUpBbjP zxp`q)T3K1y!+W<%Hg9jJw`vg*Mw-&Q9tyNHEd`IT=#UEpqjD~4E z^RfC@Z$F3^H8z_~ryHfSWm(qgbTa*XVcv?&bj$btfnHa?*um@dDVpmsF>?L3*Xu+0 zyNG<2kdR<9neuc7gy{DU#-ng495vU+K24>ZIF9oRKKZ>NR5}3gyyPRArjJ2 zf+9GneqF87jYgx)wVGJz@o_T{>vs3KTv8sMfSye0b7#*&eJ)p4MuzH$z|-elQ6iu& zBJkq1T~TGrm!v941lAN}Dx|xuy)#M#qC|icBA~R%6M*&npp9OT#pigC=nPp5g7fdQCeDBTwIKaKxO%EOa#U(w2IzM!^(@?E$JU80&VS` zsw4ubW>b_1{D(wfc2d$OhEe0SngVs&z)y})BJlKi*ZoJ2WgLpv7LrQ@Xd0=N2vB3; zSaOMgR;yDh5zy*l)Jg<&+6m=42}FRVDYX&-MAM;h_SjzWOGMK|6M@hlJ|_?D1b{O~ zcb%xIR|gRYCCsVwSC1dqf#;_$G^l_GXhlEMv$c(piwF||u`vwu!e-l0Qj(pWefUUx zZQbpLdh413tIzMB(#G&_!c^|xjwk0X-;976L=pjnXgn@jo3O}(`?iG^i6#O_)CkmG zxeWl9>h6da$t42Xkw=pC+TE}fa*4p0M-sV20F9?}B#;Q$d%jF+*QfVdjmwBCrAMCe isv!dMU5CLC`)2?-k0%YdqHBTx0000b%7 diff --git a/src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_rgb_b.png b/src/Umbraco.Web.UI/umbraco_client/colorpicker/images/custom_rgb_b.png deleted file mode 100644 index 80764e5d6dd8aac3c5ef87d83b45d29780af1fe9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1008 zcmVh+v*8l*BQUCyPMF0TmW&i*=2LJ#e`2#0~ zRsaA4c1c7*RCwC#nrlcDK^(y6c4ze-dVNsvS$Lk7-d#%KDZNO9B8i}H)rSZIAqqt8 z0YMQxK1gMO7GYLYP(hF%0x2*QLy#1V@^M=3*;VR^9(UeDy}aA)!`iamU9KIiH$ywj zhuOK=n;*mfzdQ50Qw)ExxjjCBThS(Jpxfh1O-aHI-9F`j004$z0EPtu00M#F|Aj7f zhG-Kt;^>QkKy=@(+mI#^_t>{dQ7Uu;MwBWt`i+KWzHKxWRFsz@h)w_i7?usGHrkT0 zKD)TsoR@FvXls4b(GxyKYINaT<}LZ*vqJ!YN*N!1=fMNzxw_27Kw#GGrD;0r$o{Em zr*Oc+f?rZ}o~pp02LO~789ois!r37JfFKm%ceb>?)2NlMS?`CQ&YGI)1dVFi;abap z;i(3Ot;S6{u01$1wk*~o=Qus0RH+$;@h{9dT^@(i9mA9r5-&q%BL(iMs>(uxJ`fB( zYkAvrw{d&PJy!bo78ZWG&G~w+(Qlzc)0AaHD_6$tGgjLqx8AwgnFK+EkC766sd6FC zvaBgDTR8h41E#*A8A58#=dq7}E!nvj01ynuY^%f3_gk#u0aI07tIx{t`+u@5Sf|w# z8gra3Ppkq)NH30V*qD`|QSq0b>F@IrKMF4dTKJ9u+MBYo3KOg?j4Yg0AA=H2S+6(D>UXvXavf4 z6iLM0-qj}?0VIt844nb#OyNL5;7c;7b^0X;e+ zAaz;U2rQw?MnEci3qJQrEH1Yf zQ6tdZJMg-#bAQ~f6nSu? z`yT;Fl6FhB8z}0h=7q30U!wB#JbGiP}h1#QB`^wG77#IOc zkfwEwPs2OWH3IN=z??aGm}^|W^BfN&03pfMTTL&x^*w#!5Z{Tk5rBe?K;!+F0D#*M zT7;eG8UbqgE2-(ptB@XajezJY30)%qSF>|O&h+v*8l*BQUCyPMF0TmW&i*=2LJ#e`2#0~ zRsaA4vq?ljRCwC#nO#UzVHn3B&&Qk_ilpY|_s0}>?nPou*fl}Rc5eb1HAxWC1`PusLBR4nqy_`E;96KC0+d@0;ym(%oi|yfg z_x$(ndC&X2zxj-gtMmpFpf&>hrG(yKl7$8#Cw*>SLjZuo;Q#~&0RVRU%J!1Z^$rQ} zmwJ}IY`1Uh(_^FL9O<6^I_pste+$N=s=rEqKRDJuFszy!3EUA98=rV2KbLRPF#vE- zl#}&+80~1kLz1G?r%oQ+pZBik&E1EcPBC0dXXnbwNOsLm0Dzy^*J;{|=Uw@S3#!VF zX>|sJ$s8sNZn@nSArH}L^-PS_YU5NoqiR=5JOE^8rVM_XWM{_!0KX4nMA2cakQ~TGSb(W#7`WfP_{jIVqaO2_=ywZB4+0`t6RrGF=5F>bK zPOLkvw4|`EvGqhzag0J~wV399e2Gg;OHPPh(CEBUFtqxQBU8@Lb=P0tPwZQLxpYyh zpHj{KT3+6rk({nnn9LT7)wTtt!W4|Fde7476CuD~nvI-Ac&)|&&7sp9c}fI&`-Wb40tAjTR$Fz3{g%LS zUK0W451*?SjsrkVWzm)E_t78%IF8YFt-gc4k5*R{GEQC-0g|;%YnvXsI@jqO86prB z9!e7dd1%nt((^PCFq$k|)ENFLn2M64^otuUPhDXKABg}#;IuWgKBL#SytsgI@|p+` ztVW>eUMB!Fw|B9f{3Qb9+9Rp;(Mu-}e~Ey{BZ0#I_nn4BBK|!9q`eQh+v*8l*BQUCyPMF0TmW&i*=2LJ#e`2#0~ zRsaA4fJsC_RCwC#m|aK|K^(y6c4zO?v(zImG3)ZwJU`+t%!))43VaBAEQo?ckfMkq z9|U{Ie6UO_tx!s^pn{-;9_k?o3PD0RD7CZlPA}~|?M&1A_Pp(3&tvZcw~p4^!TmE2 zv%9l9KZgJ8%=`qS-za z#zPh5bu}lek5);fvfiF9%hXIn8@AEueU)hQqkD${0Et)>{ie1kXIsXWtjzSX(&AG! z2CL1kEy{7aJfQ|2kC#>S5LK}z8vszM(|r1DqW2B~02n5Se$)8XVw?Tt@pwNNjBb~s zvP>sWlDb*O4aSeU4Uq3DNE}3`h3ei-*WU$Bf;o_Ks=KU$8mO7(?GnVj>di+RrF-J4Ao=x&$$Jw&5j&;hEf5o zHv9gPLgdf~28VegfEN14kug@ub{D8wGy;_ebu4ke?Kkj7fHwkMF#>GI+D0IV&Km*V z2=GRLHv+s7_@74LZNC9YBcN7g@7&G3i+D8Ucb3qG|*Pp$OH45fX9bIv0!pj$^1A0f^(F($ZK*zl1m* zM3;y~5rr}WdFrfEou*NLMj}nJ+U$zdO%ZKa9`#@}H`D?Enj30yT5#G3gc7Eyt@pD2 zG*!Ra{ty8pK+vXXOXuV0o7^=5aCJ$lKX-zvwBCJ+h7o`er^?OlXVmy!I(s~Hlha0k z{y*t_@Eiber{@Lz55rv}K!h(z-H%>I+~KYfU|f>8YXsom>>N301jfdv;veh+v*8l*BQUCyPMF0TmW&i*=2LJ#e`2#0~ zRsaA4Ye_^wRCwC#l-+OJWE94q*RQ?AiR0MGMw%@}VN1KRu@(A31>#H7y28o^Y!?Ut zH(bGg!wuKmGLR-VIu&VBK^my6Efv{HS2f*~kA9_T6FaFBC$Swa;+ifQNNbT06G!jO zvEJv=Ip_8Hk%dI6Skkc95Pe>@SkgK|!9827XSY}YmStfd!2&Q0^Pjhf&nISQW;{z% z(K~Te4kS+vluw`R8|?3Ww!7VeOwG>oyleNTtJiPJE8m<63lXVDD0@(HZ*TL<_X-o` zd||Zv(&(GJFIOtn)+(;wm^jIOG5r2{F|Q!A1yw`d=dbbp$f-`3eDlkhs~EpHa_%o1 zSx6-PuCIsR8xddbUS$C=3~A=dQ@?8Bg(}Pv+!E`RB-z6sA>M#a zd8Gt}hjIWVUQ~Y$Iug4PC$bhYs1mR;paLooaJeYY=K*Ess5NZBY8_`|XBNVG-(4jq z91M!kW#}P517QFa$X1cBK&>IOsd##GjsyS$pwX~T7c(@mxby(E6pSp`HPAGWB3Plw zvdCCSXt(y^x~C^mwm^(jyaOnSpYacdWMYS+t084 z^2+w%(=O@I5mKuuYq`}Q?t~JPpM7%pt#fC0-?w*!dH2B<}EheYuo*06&0P2R(PVkSzLgcj` zVW8J0_-tX~r?D$-1b;*o|4o8p{{*)!{wMeoM(~F$ngoCMosr`UvD=f=@$qZFb7M_{ zzZwY*efmZ_!FT>n%gGzf>BeHwSS#&(ny T=Ar9_QX-DN@01`G*?mYE;P(j>ieaLJ zp1@KmP7DbnxHd5epSMZF>_Ios1X7kEzM%)`8u}mxVv)EwgAVwi+aL%gVHgVE_pQ>@ zbuG%W6jfCb)Fr3i6gRMr(1H#Un@BbTwH6Q2xnfI1$74|x z`a$b3REt~>BR!+Rjm(B~!)%yB$32-1&(IZ{4HWmQa;H~;maQBTMsJ;5 wu=L25Ib=s<$1=nh_wOs&FkN?ph+v*8l*BQUCyPMF0TmW&i*=2LJ#e`2#0~ zRsaA2j7da6RCwC#n88ZJKomvKOQ$N-q%EcB2MDDFapS&Ut2tcoJKO0Z5XR@gxT5=E@BPRC5JM(-{EmRs+wi z*>P|@#ScQvT9QAwUwvX=#Ku^(QHKpv2~+}=KqXKKR03@~kh1h#z3lU2-9S7%K9wHY zZlHd@zd1k|%qmdMx=&^ql(G#p7!3Z$L#9{gzl=qFW|hX6M^<+t`|yc1Y?-gyyEotInm#MOm!f^^y*-dYoVJ8o0wy4TmX zR?7dfn;bc9{iI8VpES5yofZaYh;TjCpK_UhilywQPp{V&o!)zNr-EuAP#7%xRC{Am z6GR!1|0e8}W%vqnuQi`zyC%VuOt~~QLec?j5=hH8t&PuqF6Zm0mDYFhgqkAab@NoR n(3#>M@3IhYo&TccjBE_r(UXjnZhw#mdW^x-)z4*}Q$iB}AxU+4 diff --git a/src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/active-bg.gif b/src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/active-bg.gif deleted file mode 100644 index d608c54698e081a3deae0f1b9b827484deaeacce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89 zcmZ?wbhEHb3xI7cXAAbm`*d3l}e6x_tTajT<-q|NpP}lZBCift^8z p0SG{98JMLwcHQ}C;N`q}uj=cwv(saC#4b@4SZ=W5%`PM diff --git a/src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/hover-bg.gif b/src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/hover-bg.gif deleted file mode 100644 index fbf94fc2c1891ff891f2d3c3208cf431c35a4628..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89 zcmZ?wbhEHb-E$->CMz|NrfPt>_6 zHL)Z$MWH;iBts!2BUQoO(>H)Y2dIbvWC#PZ6vwVR9}T>mSMODQeRg(w%#PS4t~~0B LE8grwc6hbmm72G|20~mA|fB */ padding: 1px 3px 1px 1px; diff --git a/src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/title-bg.gif b/src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/title-bg.gif deleted file mode 100644 index 6a541b3bc1e4bbee3268e8a5b431e2c7e067b3b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116 zcmZ?wbhEHbWMmLyc+AI8Sy_4e_U#7`9z1*Y?D_NOFJ8QO`}Xa-ckkZ6fB*UO=PzHr zeEwc6hbmm72G|20~mA|fBtHB8VX&K*sP!FEoV37-@-w z0R<@%0vdJ3g^a|I?Pj7#(uKh`L|Finie4=6J7+yFJ!?;2&hK|#eqYx=!aJTkdGdK~ zpXAvT*|;uzpq}wF^XC|f7Hy%(3T3cCne7xg0NE&VQse^UpvX;;2at=R3KMyuTq^;2 zC|Y8oD$05(`vPTsz*a#S0tVY?u&+|C9=JNny<&2Wlxxu0Hl1B<&bOHJt%U0!T(_BC zgSodcw-fkw;8z2`7Wg+!zQf>qKnNTBb`ZKuVKa!`hOo;JcIVTBAiY~i59ZSeL+aC| z!@BeVWcDfiw+cT5^1&QG406AoIcj8%=$U~We@)9obve2q{HDt%A^V9eOz8`!W#PUo z{Glyegxu${_yBUTjF`%Z|48DTBtDfyM6y>wiIePzB&H=%lq8Adt`lWUQ^v6JD^_ko zVMxpW0>xpRzl-zJT46#fEanU@Jl2Z$aN#etkRrv0xcG#WQfd*YMOH0xfG5?`f?CQG zRZvSM70ars5}YAe!5FKgv{(qnpg$pmfIbJN473Ck12uqBc&`6pfd*o?R4~A_FpDaG zf^k#kwtL0q(ctCQJ>$_C|68Xo9Y5B%<>HN|rPXUc)#iLZ4#(@;I^sWF!BdIIrn4`- z_ReJJ%ebfY&*2}GiP+Mnwl{8s9^RRq>F%t|b=gjA`^L{W-2=&FGRjoj?dL}ulChUN ztGti>UrgEp&DJF~TgO`%XJ0rw;D5cjZkdINhI+^4JxpY(BDmw(Y^2$Dw;%8S`6J8C z+7+J0mD495S)75uciVQyzK?~28yvw)3bIF5-kaz;!0dbQmF@mI-kmt>7+F`d+N zt#PY&(k@T!8mH}j>(;M_TI-IV)AU=8{=a)`lc$<@jm#z)SE${(>{g=Z!tz!BW;gbH Q9Q54|91Yd$>cA zcV=g2cV--{@!&!I-n?WE@8x~(dmofif^qy4@G8*nANW69HN1QADhz`>%YkX+TxJ<9 zm5Knu7)O(3i>EROBfY5Ev-SI%4-rn-7h2V=x)3l+X)3rxM%PbL4g{DZeh#FeoBB78 zJZsDYAdmzo(H85~0;V#BGFC5}eYIkW4~sg@Do%p0+pignoa%Z3L)OEt*Vd46N5P%5&vU1YiFIc&XC+Sz9t9jhOM zb0@lYz4Cl(h+;=7mDI$6mkLA(c?``?=dKE8wWW7=~fmZm*7iDH~&;HXCoWVb$S8r-H$}PnPUb@2n;h_t&L|9^T#^yxcpn z>ov!V1kVpn>ifrE2kiy-66Y-G&NSY<-F>j8D+3{7qYn20j4ROwMeAG+C7NAodVhUCC9?k3)z zo&)fFqoJ`c8uhYKCOx}C7$3Rhcu+mPA}0f58iR=&y?beW{q7>+ZAJSr@$=-*D2Ni~ zuG5^*kd^WE1-@PI@dGtF-_lQ4HxuVX2LQ?eQ9_E-G(t=Uwox;UMUW4=hWalA-M6~s z^v+MSUU@ldaqze$aKVEF%-6~O6MgQH2xBj`l<}<4o379G3}zu1KCA{N-wxo6#*2vL z`Dl?wF-|GC86_IBoy#9uR?O?i`3xh-6=kz!BsXPNP`|TB3f{J>IJc$?+;1Dd>4&WQ znG?vSN#Cwcr!^s#TSAul49*M$+*?>+I~}&BsI8 z4CSW1U^$%3!zkt(A97`5kc}s0A>}Dhxt+EsDXk TX}TB=00000NkvXXu0mjf-(wsi diff --git a/src/Umbraco.Web.UI/umbraco_client/propertypane/images/proppane_bg.png b/src/Umbraco.Web.UI/umbraco_client/propertypane/images/proppane_bg.png deleted file mode 100644 index 116511a5caab86828cdb4e970aebd464b68184fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 554 zcmV+_0@eMAP)Wfe00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUy=TJ;kMXRf; z`}_O;{{H>_{rLF!t*x!Eudlhex$*Jwva+(QtgOz?&d|`%)6>)4-QCvK*4o+9?9@9*>T^Y!)h_xJbq_V%;0v)kL-xVX6R@bLNh`L3?6`uh6({QS+$ z&Bw>b&(F`iyu9Jz;kC83ySux=!NJAF#mmdf(b3WD?CiZbW=H}+^?(XvP^1#5r zwzjsnx3|~V*Yx!Cy1Kfsu&~wD)xN&I$jHdb%F5v2;Ogq?!otG8zrVx7!?d)t=;-Lw z)YQbp#M#-|v9Yn=-{0ls<^TWx!n_`N0002VNklbsh*cRLhs)Aj?jvy^O8-kV~Aw1iH sl<-Un&tDN485tQF85tS*?@xdM0QB5^B4OrgApigX07*qoM6N<$g34Pl;Q#;t diff --git a/src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/arrawBack.gif b/src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/arrawBack.gif deleted file mode 100644 index 9d3f0ca6869d1af01a28961dd171ec57bc826569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 834 zcmZ?wbhEHbWM>dz_|C)t1poj4hm#=wC>RZa5fcK6KUo+V7?>DzKt2ZL2?h>%238Ik ij|~eBHggCo{R#gdf4GrP-f0R);KHNb68dZ`4AubXbQK2x diff --git a/src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/arrowForward.gif b/src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/arrowForward.gif deleted file mode 100644 index bf3f49e02463fa56ac1e3bf90a7fa8cb14e575de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 834 zcmZ?wbhEHbWM>dz_|C)t1poj4hm#=wC>RZa5fcK6KUo+V7?>DzKt2ZL2?h>%238Ik ij|~eBHggC|{nU;fa}+&IBQ df``db!9XI#sGq%6X<~@hWY2{S2U{2ztO2IrWT*fD diff --git a/src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/button/buttonbg.gif b/src/Umbraco.Web.UI/umbraco_client/scrollingmenu/images/button/buttonbg.gif deleted file mode 100644 index 6268a1bc0d8b6ae8fc753bf2b5f46e5e9106ef81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmZ?wbhEHb6k`x)IKsg2|K9Tdm#6)IvHkz+egAJQ`v3OO|My4#KV19&!o>eqXa4_k z=Ku9M|L-jM|LMg4him@7-2MN?{Qu9k{J%K)|C0^>AFTHF&S$_26o0aS<#j+L$W8{< zz6902l*}%#RqJ%L%?fnRZ_c<=vF>%!!~X{o0!%cd`aPDsIGAHK-+y9)*V?GHi#p0q zzb$i}yrIHJt()(~ma4sCmiIpFh|ybg#j0_B73_2hs zAUhdY`xF%VQZl=OSFO|04l2+&zd7P=#k$u?PyQcB2vE_K>i1Z3;$V)|eE*BdUTdS) zDs_~dep}|+wV}dCty}EHma4sChW9?~h|$Zs@%~3m+>{r;|2H%!vA49gwYMvAbocc3 z^(nDUnmlFdR3(;~vu4kkqr|#k;iAQhm6(^WSh;GI64Sc%8#ZoKV%)lI`;HyzyLT%x GSOWm&o_o*$ From 8664d3cd3565f3f5e7d1b60e1631e6036bda9772 Mon Sep 17 00:00:00 2001 From: Shane Marsden Date: Thu, 3 Nov 2016 17:03:16 +0000 Subject: [PATCH 023/510] forgot to add file to commit --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 220 ----------------------- 1 file changed, 220 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 6f2728a241..06b339ed47 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -624,14 +624,6 @@ - - - - - - - - @@ -744,7 +736,6 @@ - @@ -778,9 +769,6 @@ - - - @@ -789,8 +777,6 @@ - - @@ -1471,53 +1457,35 @@ - - - - - - - - - - - - - - - - - - @@ -1525,18 +1493,6 @@ - - - - - - - - - - - - @@ -1566,14 +1522,11 @@ - - - @@ -1590,19 +1543,6 @@ - - - - - - - - - - - - - @@ -1663,7 +1603,6 @@ - @@ -1674,7 +1613,6 @@ - @@ -1713,25 +1651,9 @@ - - - - - - - - - - - - - - - - @@ -1744,15 +1666,12 @@ - - - @@ -1771,35 +1690,14 @@ - - - - - - - - - - - - - - - - - - - - - @@ -1884,12 +1782,6 @@ Designer - - - - - - @@ -2003,11 +1895,9 @@ - - @@ -2020,8 +1910,6 @@ UserControl - - @@ -2031,32 +1919,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -2134,100 +1998,35 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2293,18 +2092,7 @@ - - - - - - - - - - - @@ -2325,13 +2113,7 @@ - - - - - - @@ -2383,8 +2165,6 @@ - - From b3938cd7b2a491d1c1a5aa8abd34ab245a3d2931 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Tue, 22 Nov 2016 20:39:18 +0100 Subject: [PATCH 024/510] Fix sorting of grid editor config --- .../propertyeditors/grid/grid.prevalues.html | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html index eaffa56d14..3cd509cd4c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html @@ -9,30 +9,29 @@ ui-sortable ng-model="model.value.templates"> -

  • +
  • + class="preview-rows layout" style="margin-top: 5px; margin-bottom: 20px; float:left">
    + ng-class="{last:$last}" + ng-repeat="section in template.sections | filter:zeroWidthFilter" + ng-style="{width: percentage(section.grid) + '%', height: '60px', 'max-width': '100%'}">
    -
  • @@ -55,7 +54,7 @@ ui-sortable ng-model="model.value.layouts"> -
  • +
  • @@ -64,7 +63,7 @@
    + ng-style="{width: percentage(area.grid) + '%', 'max-width': '100%'}">
    @@ -79,12 +78,11 @@
    -
  • @@ -98,7 +96,7 @@ - + + ng-model="model.value.config">
  • @@ -133,8 +131,11 @@ - -
      + +
        From 6a5002495c1408ba9027f9daa003e47c31f20c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gregersen?= Date: Thu, 24 Nov 2016 08:58:57 +0100 Subject: [PATCH 025/510] use already defined variable, instead of mapping the path twice also fixed a slight indentation issue --- src/Umbraco.Web/UmbracoApplication.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 0f6fc03b80..b9650a8230 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -34,9 +34,9 @@ namespace Umbraco.Web var appPluginFolder = IOHelper.MapPath("~/App_Plugins/"); if (Directory.Exists(appPluginFolder)) { - _mw = new ManifestWatcher(LoggerResolver.Current.Logger); - _mw.Start(Directory.GetDirectories(IOHelper.MapPath("~/App_Plugins/"))); - } + _mw = new ManifestWatcher(LoggerResolver.Current.Logger); + _mw.Start(Directory.GetDirectories(appPluginFolder)); + } } } From f266c7891563e93b30f6442bd69eb65465055ce4 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Mon, 5 Dec 2016 22:26:09 +0100 Subject: [PATCH 026/510] Fixed issues with user creation and deletion --- .../AddUserGroupTables.cs | 13 ++++-- .../Repositories/UserGroupRepository.cs | 1 + .../Repositories/UserRepository.cs | 4 ++ src/Umbraco.Core/Services/UserService.cs | 2 +- .../umbraco/config/create/UI.xml | 8 ---- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../umbraco/users/EditUser.aspx.cs | 2 - .../umbraco/users/UserGroupTasks.cs | 42 +++++++++++++++++++ 8 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/users/UserGroupTasks.cs diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs index 603517bbe3..ea566c0238 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.SqlSyntax; @@ -16,11 +17,12 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero public override void Up() { var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); AddNewTables(tables); MigrateUserPermissions(); MigrateUserTypesToGroups(); - DeleteOldTables(tables); + DeleteOldTables(tables, constraints); } private void AddNewTables(string[] tables) @@ -162,7 +164,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero )"); } - private void DeleteOldTables(string[] tables) + private void DeleteOldTables(string[] tables, Tuple[] constraints) { if (tables.InvariantContains("umbracoUser2App")) { @@ -176,6 +178,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero if (tables.InvariantContains("umbracoUserType") && tables.InvariantContains("umbracoUser")) { + if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id"))) + { + Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser"); + } + Delete.Column("userType").FromTable("umbracoUser"); Delete.Table("umbracoUserType"); } diff --git a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs index b13033c7b3..d214c0d555 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs @@ -194,6 +194,7 @@ namespace Umbraco.Core.Persistence.Repositories { var list = new List { + "DELETE FROM umbracoUser2UserGroup WHERE userGroupId = @Id", "DELETE FROM umbracoUserGroup2App WHERE userGroupId = @Id", "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @Id", "DELETE FROM umbracoUserGroup WHERE id = @Id" diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index cad213d1a0..1bc86c1683 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -284,6 +284,10 @@ namespace Umbraco.Core.Persistence.Repositories /// An enumerable list of public IEnumerable GetGroupsForUser(int userId) { + var tables = SqlSyntax.GetTablesInSchema(ApplicationContext.Current.DatabaseContext.Database).ToArray(); + if (tables.InvariantContains("umbracoUserGroup") == false) + return new List(); + var sql = new Sql(); sql.Select("*") .From() diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 81a71e0f05..9499a142d0 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -321,7 +321,7 @@ namespace Umbraco.Core.Services public string GetDefaultMemberType() { // User types now being removed, there is no default user type to return - throw new NotImplementedException(); + return "None"; } /// diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index ba3df007e7..3edb5fcedc 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -213,14 +213,6 @@ - -
        User Types
        - /create/simple.ascx - - - - -
        User Groups
        /create/simple.ascx diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c2b5665f4c..411657f5fc 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -429,6 +429,7 @@ ASPXCodeBehind + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index 83c30ddc25..6d7c4056fd 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -39,7 +39,6 @@ namespace umbraco.cms.presentation.user protected TextBox lname = new TextBox(); protected PlaceHolder passw = new PlaceHolder(); protected TextBox email = new TextBox(); - protected DropDownList userType = new DropDownList(); protected DropDownList userLanguage = new DropDownList(); protected CheckBox NoConsole = new CheckBox(); protected CheckBox Disabled = new CheckBox(); @@ -162,7 +161,6 @@ namespace umbraco.cms.presentation.user pp.addProperty(ui.Text("user", "loginname", UmbracoUser), lname); pp.addProperty(ui.Text("user", "password", UmbracoUser), passw); pp.addProperty(ui.Text("email", UmbracoUser), email); - pp.addProperty(ui.Text("user", "usertype", UmbracoUser), userType); pp.addProperty(ui.Text("user", "language", UmbracoUser), userLanguage); // Media / content root nodes diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserGroupTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserGroupTasks.cs new file mode 100644 index 0000000000..0aae75d278 --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserGroupTasks.cs @@ -0,0 +1,42 @@ +using Umbraco.Web.UI; +using umbraco.BusinessLogic; + +namespace umbraco.cms.presentation.user +{ + public class UserGroupTasks : LegacyDialogTask + { + public override bool PerformSave() + { + try + { + var u = UserGroup.MakeNew(Alias, "", Alias); + _returnUrl = string.Format("users/EditUserGroup.aspx?id={0}", u.Id); + return true; + } + catch + { + return false; + } + } + + public override bool PerformDelete() + { + var userGroup = UserGroup.GetUserGroup(ParentID); + if (userGroup == null) + return false; + userGroup.Delete(); + return true; + } + + private string _returnUrl = ""; + public override string ReturnUrl + { + get { return _returnUrl; } + } + + public override string AssignedApp + { + get { return DefaultApps.users.ToString(); } + } + } +} \ No newline at end of file From 9589500412476918793a44d04b040cac9894d8ef Mon Sep 17 00:00:00 2001 From: Pete Duncanson Date: Fri, 6 Jan 2017 20:40:09 +0000 Subject: [PATCH 027/510] Fixed error label showing when you do a retry Should be a nice and easy one, the bool for if the DB connection is valid or not is never cleared on when you do a retry. http://issues.umbraco.org/issue/U4-9193 --- .../installer/steps/database.controller.js | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index 5b3e174930..c4ac65bff1 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -1,28 +1,35 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseController", function($scope, $http, installerService){ $scope.checking = false; + $scope.invalidDbDns = false; + $scope.dbs = [ - {name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, - {name: 'Microsoft SQL Server', id: 1}, - { name: 'Microsoft SQL Azure', id: 3 }, - { name: 'MySQL', id: 2 }, - {name: 'Custom connection string', id: -1}]; + { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, + { name: 'Microsoft SQL Server', id: 1}, + { name: 'Microsoft SQL Azure', id: 3 }, + { name: 'MySQL', id: 2 }, + { name: 'Custom connection string', id: -1} + ]; - if(installerService.status.current.model.dbType === undefined){ + if ( installerService.status.current.model.dbType === undefined ) { installerService.status.current.model.dbType = 0; } - $scope.validateAndForward = function(){ - if(!$scope.checking && this.myForm.$valid){ + $scope.validateAndForward = function(){ + if ( !$scope.checking && this.myForm.$valid ) { $scope.checking = true; + $scope.invalidDbDns = false; + var model = installerService.status.current.model; - $http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection", - model).then(function(response){ + $http.post( + Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection", + model ).then( function( response ) { - if(response.data === "true"){ + if ( response.data === "true" ) { installerService.forward(); - }else{ + } + else { $scope.invalidDbDns = true; } @@ -33,4 +40,4 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll }); } }; -}); \ No newline at end of file +}); From ea13989b7e1055a7604f8f37aa8cf825516894bc Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 2 Mar 2017 23:19:45 +0100 Subject: [PATCH 028/510] Allow leading whitespace in textstring prevalue editor --- .../src/views/prevalueeditors/textstring.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html index 8fcfc8b5c6..a394bdefe4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 8d8bb4e23704c01b1d44c274b44836f2557bc7ab Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 31 Mar 2017 17:24:18 +0200 Subject: [PATCH 029/510] fix HasImage() --- src/Umbraco.Web/Models/ImageCropDataSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Models/ImageCropDataSet.cs b/src/Umbraco.Web/Models/ImageCropDataSet.cs index 4c5a68a6b9..00f46dd2d7 100644 --- a/src/Umbraco.Web/Models/ImageCropDataSet.cs +++ b/src/Umbraco.Web/Models/ImageCropDataSet.cs @@ -76,7 +76,7 @@ namespace Umbraco.Web.Models public bool HasImage() { - return string.IsNullOrEmpty(Src); + return ! string.IsNullOrEmpty(Src); } public string ToHtmlString() From ece73578c86525a217783bd715e6333dc84059dc Mon Sep 17 00:00:00 2001 From: Jeavon Date: Tue, 4 Apr 2017 12:08:10 +0100 Subject: [PATCH 030/510] Tweaks to RelatedLinks - moved Content property from RelatedLinkBase to RelatedLink and populated Content property for non UDI RelatedLink editor --- src/Umbraco.Web/Models/RelatedLink.cs | 15 +++++++++------ src/Umbraco.Web/Models/RelatedLinkBase.cs | 3 --- .../RelatedLinksEditorValueConvertor.cs | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/Models/RelatedLink.cs b/src/Umbraco.Web/Models/RelatedLink.cs index 884bdebeef..2dcb63dd5c 100644 --- a/src/Umbraco.Web/Models/RelatedLink.cs +++ b/src/Umbraco.Web/Models/RelatedLink.cs @@ -1,8 +1,11 @@ -namespace Umbraco.Web.Models +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models { - public class RelatedLink : RelatedLinkBase - { - public int? Id { get; internal set; } - internal bool IsDeleted { get; set; } - } + public class RelatedLink : RelatedLinkBase + { + public int? Id { get; internal set; } + internal bool IsDeleted { get; set; } + public IPublishedContent Content { get; set; } + } } diff --git a/src/Umbraco.Web/Models/RelatedLinkBase.cs b/src/Umbraco.Web/Models/RelatedLinkBase.cs index b347e25e0a..c2077ce4a9 100644 --- a/src/Umbraco.Web/Models/RelatedLinkBase.cs +++ b/src/Umbraco.Web/Models/RelatedLinkBase.cs @@ -1,5 +1,4 @@ using Newtonsoft.Json; -using Umbraco.Core.Models; namespace Umbraco.Web.Models { @@ -15,7 +14,5 @@ namespace Umbraco.Web.Models public bool IsInternal { get; set; } [JsonProperty("type")] public RelatedLinkType Type { get; set; } - [JsonIgnore] - public IPublishedContent Content { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs index b95644d9ca..ae63c0a2b3 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs @@ -104,6 +104,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { relatedLink.Id = contentId; relatedLink = CreateLink(relatedLink); + relatedLink.Content = UmbracoContext.Current.ContentCache.GetById(contentId); } else { From 11b97c5291d01d292ffb02a4479b8a197f1cd379 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Thu, 20 Apr 2017 09:45:48 +0100 Subject: [PATCH 031/510] Removed unnecessary null check --- src/Umbraco.Core/Services/SectionService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index 9de793e37d..dcfe3748ec 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -212,7 +212,7 @@ namespace Umbraco.Core.Services public void MakeNew(string name, string alias, string icon) { var sections = GetSections(); - var nextSortOrder = sections != null && sections.Any() ? sections.Max(x => x.SortOrder) + 1 : 1; + var nextSortOrder = sections.Any() ? sections.Max(x => x.SortOrder) + 1 : 1; MakeNew(name, alias, icon, nextSortOrder); } From 53ef2e356a0af8a7cb15029220d47c375bf65a28 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 3 May 2017 09:30:29 +0200 Subject: [PATCH 032/510] add new temp users tree --- src/Umbraco.Core/Constants-Applications.cs | 2 + src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + src/Umbraco.Web/Trees/UsersTreeController.cs | 44 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 4 files changed, 48 insertions(+) create mode 100644 src/Umbraco.Web/Trees/UsersTreeController.cs diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index fd9476a8ab..8b9be02f4f 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -118,6 +118,8 @@ public const string Scripts = "scripts"; + public const string Users = "usersV2"; + //TODO: Fill in the rest! } } diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index ecea653650..777728582f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1370,6 +1370,7 @@ To manage your website, simply open the Umbraco back office and start adding con Templates XSLT Files Analytics + Users New update ready diff --git a/src/Umbraco.Web/Trees/UsersTreeController.cs b/src/Umbraco.Web/Trees/UsersTreeController.cs new file mode 100644 index 0000000000..65c289006a --- /dev/null +++ b/src/Umbraco.Web/Trees/UsersTreeController.cs @@ -0,0 +1,44 @@ +using System.Net.Http.Formatting; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Users)] + [Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 3)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class UsersTreeController : TreeController + { + /// + /// Helper method to create a root model for a tree + /// + /// + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + + //this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Users, Constants.Trees.Users, "overview"); + root.Icon = "icon-users"; + + return root; + } + + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var baseUrl = Constants.Applications.Users + "/users/"; + + var nodes = new TreeNodeCollection(); + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + return menu; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d3e31fa366..d534dc2ae4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -433,6 +433,7 @@ + From 8f69efe331295b9c4e8b3d9304dae434c076324b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 4 May 2017 19:16:52 +1000 Subject: [PATCH 033/510] It builds! --- .../Initial/DatabaseSchemaCreation.cs | 2 +- .../AddUserGroupTables.cs | 6 +- .../Repositories/PermissionRepository.cs | 158 ++++++++---------- .../Repositories/UserGroupRepository.cs | 2 +- .../Repositories/UserRepository.cs | 6 +- src/Umbraco.Core/Services/UserService.cs | 23 +-- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Cache/CacheRefresherEventHandlerTests.cs | 4 +- .../Repositories/ContentRepositoryTest.cs | 2 +- .../Repositories/UserGroupRepositoryTest.cs | 2 +- .../Repositories/UserRepositoryTest.cs | 7 +- .../Scoping/ScopedRepositoryTests.cs | 3 +- .../Services/UserServiceTests.cs | 11 +- .../Cache/CacheRefresherEventHandler.cs | 28 +--- src/Umbraco.Web/Umbraco.Web.csproj | 2 + .../umbraco/users/EditUser.aspx.cs | 14 +- 16 files changed, 121 insertions(+), 151 deletions(-) rename src/Umbraco.Core/Persistence/Migrations/Upgrades/{TargetVersionSevenSixZero => TargetVersionSevenSevenZero}/AddUserGroupTables.cs (99%) diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 609087c4bc..c4db9eacf9 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -85,7 +85,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //46, removed: UmbracoDeployChecksumDto //47, removed: UmbracoDeployDependencyDto {48, typeof (RedirectUrlDto) }, - {49, typeof (LockDto) } + {49, typeof (LockDto) }, {49, typeof (UserGroupDto) }, {50, typeof (User2UserGroupDto) }, {51, typeof (UserGroup2NodePermissionDto) }, diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs similarity index 99% rename from src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs rename to src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs index ea566c0238..62b75bbf7a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddUserGroupTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs @@ -1,13 +1,13 @@ using System; +using System.Data; using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.SqlSyntax; -using System.Data; -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero { - [Migration("7.6.0", 100, GlobalSettings.UmbracoMigrationName)] + [Migration("7.6.0", 100, Constants.System.UmbracoMigrationName)] public class AddUserGroupTables : MigrationBase { public AddUserGroupTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index 5cbffdeee5..3c7273eb0b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -1,7 +1,5 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Dynamic; using System.Globalization; using System.Linq; using System.Text; @@ -12,18 +10,8 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; using CacheKeys = Umbraco.Core.Cache.CacheKeys; using Umbraco.Core.Cache; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { @@ -67,8 +55,8 @@ namespace Umbraco.Core.Persistence.Repositories sql.Select("*") .From() .Where(whereCriteria); - var result = _unitOfWork.Database.Fetch(sql).ToArray(); - // ToArray() to ensure it's all fetched from the db once + var result = _unitOfWork.Database.Fetch(sql).ToArray(); + // ToArray() to ensure it's all fetched from the db once return ConvertToPermissionList(result); }, GetCacheTimeout(), @@ -154,8 +142,8 @@ namespace Umbraco.Core.Persistence.Repositories .Where(dto => dto.NodeId == entityId) .OrderBy(dto => dto.NodeId); - var result = _unitOfWork.Database.Fetch(sql).ToArray(); - // ToArray() to ensure it's all fetched from the db once + var result = _unitOfWork.Database.Fetch(sql).ToArray(); + // ToArray() to ensure it's all fetched from the db once return ConvertToPermissionList(result); } @@ -170,37 +158,34 @@ namespace Umbraco.Core.Persistence.Repositories /// public void ReplacePermissions(int groupId, IEnumerable permissions, params int[] entityIds) { - var db = _unitOfWork.Database; - - //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit - var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)"; + var db = _unitOfWork.Database; + + //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)"; foreach (var idGroup in entityIds.InGroupsOf(2000)) - { - db.Execute(sql, new {groupId = groupId, nodeIds = idGroup}); - } - - var toInsert = new List(); + { + db.Execute(sql, new { groupId = groupId, nodeIds = idGroup }); + } + + var toInsert = new List(); foreach (var p in permissions) { foreach (var e in entityIds) - { - toInsert.Add(new UserGroup2NodePermissionDto + { + toInsert.Add(new UserGroup2NodePermissionDto { NodeId = e, - Permission = p.ToString(CultureInfo.InvariantCulture), - UserGroupId = groupId + Permission = p.ToString(CultureInfo.InvariantCulture), + UserGroupId = groupId }); } } - _unitOfWork.Database.BulkInsertRecords(toInsert, _sqlSyntax); - - //Raise the event - //TODO: FIX THIS - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(toInsert), false)); - AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(toInsert), false), this) - + _unitOfWork.Database.BulkInsertRecords(toInsert, _sqlSyntax); + + //Raise the event + _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(toInsert), false)); + } /// @@ -211,30 +196,27 @@ namespace Umbraco.Core.Persistence.Repositories /// public void AssignPermission(int groupId, char permission, params int[] entityIds) { - var db = _unitOfWork.Database; - var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)"; - db.Execute(sql, - new - { - groupId = groupId, - permission = permission.ToString(CultureInfo.InvariantCulture), - entityIds = entityIds - }); - - var actions = entityIds.Select(id => new UserGroup2NodePermissionDto + var db = _unitOfWork.Database; + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)"; + db.Execute(sql, + new + { + groupId = groupId, + permission = permission.ToString(CultureInfo.InvariantCulture), + entityIds = entityIds + }); + + var actions = entityIds.Select(id => new UserGroup2NodePermissionDto { NodeId = id, - Permission = permission.ToString(CultureInfo.InvariantCulture), - UserGroupId = groupId + Permission = permission.ToString(CultureInfo.InvariantCulture), + UserGroupId = groupId }).ToArray(); - _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); - - //Raise the event - //TODO: Fix this - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); - AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(actions), false), this) + _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); + + //Raise the event + _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); } @@ -246,31 +228,28 @@ namespace Umbraco.Core.Persistence.Repositories /// public void AssignEntityPermission(TEntity entity, char permission, IEnumerable groupIds) { - var db = _unitOfWork.Database; - var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)"; - db.Execute(sql, - new - { - nodeId = entity.Id, - permission = permission.ToString(CultureInfo.InvariantCulture), - groupIds = groupIds - }); - - var actions = groupIds.Select(id => new UserGroup2NodePermissionDto + var db = _unitOfWork.Database; + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)"; + db.Execute(sql, + new + { + nodeId = entity.Id, + permission = permission.ToString(CultureInfo.InvariantCulture), + groupIds = groupIds + }); + + var actions = groupIds.Select(id => new UserGroup2NodePermissionDto { NodeId = entity.Id, - Permission = permission.ToString(CultureInfo.InvariantCulture), - UserGroupId = id + Permission = permission.ToString(CultureInfo.InvariantCulture), + UserGroupId = id }).ToArray(); _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); //Raise the event - //TODO: Fix this - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); - AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(actions), false), this) - + _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); + } /// @@ -283,26 +262,23 @@ namespace Umbraco.Core.Persistence.Repositories /// public void ReplaceEntityPermissions(EntityPermissionSet permissionSet) { - var db = _unitOfWork.Database; - var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId"; - db.Execute(sql, new {nodeId = permissionSet.EntityId}); - - var actions = permissionSet.PermissionsSet.Select(p => new UserGroup2NodePermissionDto + var db = _unitOfWork.Database; + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId"; + db.Execute(sql, new { nodeId = permissionSet.EntityId }); + + var actions = permissionSet.PermissionsSet.Select(p => new UserGroup2NodePermissionDto { NodeId = permissionSet.EntityId, - Permission = p.Permission, - UserGroupId = p.UserGroupId + Permission = p.Permission, + UserGroupId = p.UserGroupId }).ToArray(); - _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); - - //Raise the event - //TODO: Fix this - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); - AssignedPermissions.RaiseEvent( - new SaveEventArgs(ConvertToPermissionList(actions), false), this) - - } + _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); + + //Raise the event + _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); + + } public static event TypedEventHandler, SaveEventArgs> AssignedPermissions; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs index d214c0d555..f87561e32f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly CacheHelper _cacheHelper; - public UserGroupRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public UserGroupRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cacheHelper, logger, sqlSyntax) { _cacheHelper = cacheHelper; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 994c07d13e..7456bfc88c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -20,12 +20,12 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class UserRepository : PetaPocoRepositoryBase, IUserRepository { - private readonly CacheHelper _cacheHelper; + //private readonly CacheHelper _cacheHelper; - public UserRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public UserRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cacheHelper, logger, sqlSyntax) { - _cacheHelper = cacheHelper; + //_cacheHelper = cacheHelper; } #region Overrides of RepositoryBase diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 066823ca76..0c1e1fa63f 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -612,7 +612,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.GetUnitOfWork()) { - var repository = RepositoryFactory.CreateUserGroupRepository(uow) + var repository = RepositoryFactory.CreateUserGroupRepository(uow); repository.AssignGroupPermission(groupId, permission, entityIds); uow.Commit(); } @@ -641,7 +641,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); + var repository = RepositoryFactory.CreateUserRepository(uow); return repository.GetGroupsForUser(userId); } } @@ -653,9 +653,12 @@ namespace Umbraco.Core.Services /// public IUserGroup GetUserGroupByAlias(string alias) { - using (var repository = RepositoryFactory.CreateUserGroupRepository(UowProvider.GetUnitOfWork())) + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { + var repository = RepositoryFactory.CreateUserGroupRepository(uow); var query = Query.Builder.Where(x => x.Alias == alias); + var contents = repository.GetByQuery(query); + return contents.SingleOrDefault(); } } @@ -682,7 +685,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { - var repository = RepositoryFactory.CreateUserGroupRepository(uow) + var repository = RepositoryFactory.CreateUserGroupRepository(uow); var query = Query.Builder.Where(x => x.Name == name); return repository.GetByQuery(query).SingleOrDefault(); } @@ -835,7 +838,7 @@ namespace Umbraco.Core.Services public IEnumerable GetPermissions(IUserGroup group, bool directlyAssignedOnly, params int[] nodeIds) { - using (var uow = UowProvider.GetUnitOfWork();) + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateUserGroupRepository(uow); var explicitPermissions = repository.GetPermissionsForEntities(group.Id, nodeIds); @@ -951,11 +954,11 @@ namespace Umbraco.Core.Services : int.Parse(path); } - private static bool IsNotNullActionPermission(EntityPermission x) - { - const string NullActionChar = "-"; - return string.Join(string.Empty, x.AssignedPermissions) != NullActionChar; - } + //private static bool IsNotNullActionPermission(EntityPermission x) + //{ + // const string NullActionChar = "-"; + // return string.Join(string.Empty, x.AssignedPermissions) != NullActionChar; + //} /// /// Checks in a set of permissions associated with a user for those related to a given nodeId diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c6ccd8495e..1f3f352292 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -521,13 +521,13 @@ - + diff --git a/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs b/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs index a5036188fd..61f526a046 100644 --- a/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs +++ b/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs @@ -32,8 +32,8 @@ namespace Umbraco.Tests.Cache new EventDefinition(null, ServiceContext.SectionService, new EventArgs(), "Deleted"), new EventDefinition(null, ServiceContext.SectionService, new EventArgs(), "New"), - new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs(Enumerable.Empty())), new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs(Enumerable.Empty())), diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index f8ff391c32..a3e5510941 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -64,7 +64,7 @@ namespace Umbraco.Tests.Persistence.Repositories return repository; } - private UserGroupRepository CreateUserGroupRepository(IDatabaseUnitOfWork unitOfWork) + private UserGroupRepository CreateUserGroupRepository(IScopeUnitOfWork unitOfWork) { return new UserGroupRepository(unitOfWork, CacheHelper, Logger, SqlSyntax); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs index 6a2412bc63..1b669fdf60 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs @@ -30,7 +30,7 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); } - private UserGroupRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) + private UserGroupRepository CreateRepository(IScopeUnitOfWork unitOfWork) { return new UserGroupRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index f13175f563..62b22f681a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -28,14 +28,13 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); } - private UserRepository CreateRepository(IScopeUnitOfWork unitOfWork, out UserTypeRepository userTypeRepository) + private UserRepository CreateRepository(IScopeUnitOfWork unitOfWork) { - userTypeRepository = new UserTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); - var repository = new UserRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, userTypeRepository); + var repository = new UserRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); return repository; } - private UserGroupRepository CreateUserGroupRepository(IDatabaseUnitOfWork unitOfWork) + private UserGroupRepository CreateUserGroupRepository(IScopeUnitOfWork unitOfWork) { return new UserGroupRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); } diff --git a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs index 62bd17e122..ddbdf82f7f 100644 --- a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs @@ -80,8 +80,7 @@ namespace Umbraco.Tests.Scoping var service = ApplicationContext.Services.UserService; var globalCache = ApplicationContext.ApplicationCache.IsolatedRuntimeCache.GetOrCreateCache(typeof(IUser)); - var userType = service.GetUserTypeByAlias("admin"); - var user = (IUser) new User("name", "email", "username", "rawPassword", userType); + var user = (IUser) new User("name", "email", "username", "rawPassword"); service.Save(user); // global cache contains the entity diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index e05b9e6225..6c513e0956 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -535,11 +535,10 @@ namespace Umbraco.Tests.Services public void Cannot_Create_User_With_Empty_Username() { // Arrange - var userService = ServiceContext.UserService; - var userType = userService.GetUserTypeByAlias("admin"); + var userService = ServiceContext.UserService; // Act & Assert - Assert.Throws(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io", userType)); + Assert.Throws(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io")); } [Test] @@ -547,8 +546,7 @@ namespace Umbraco.Tests.Services { // Arrange var userService = ServiceContext.UserService; - var userType = userService.GetUserTypeByAlias("admin"); - var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io", userType); + var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io"); user.Username = string.Empty; // Act & Assert @@ -560,8 +558,7 @@ namespace Umbraco.Tests.Services { // Arrange var userService = ServiceContext.UserService; - var userType = userService.GetUserTypeByAlias("admin"); - var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io", userType); + var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io"); user.Name = string.Empty; // Act & Assert diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 03898f8af4..2a30a90635 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -58,30 +58,12 @@ namespace Umbraco.Web.Cache () => SectionService.Deleted -= SectionService_Deleted); Bind(() => SectionService.New += SectionService_New, () => SectionService.New -= SectionService_New); - //bind to user / user group events - //TODO: FIX THIS - UserService.SavedUserGroup += UserServiceSavedUserGroup; - UserService.DeletedUserGroup += UserServiceDeletedUserGroup; - UserService.SavedUser += UserServiceSavedUser; - UserService.DeletedUser += UserServiceDeletedUser; - - //Bind to dictionary events - - LocalizationService.DeletedDictionaryItem += LocalizationServiceDeletedDictionaryItem; - LocalizationService.SavedDictionaryItem += LocalizationServiceSavedDictionaryItem; - - //Bind to data type events - //NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 - - DataTypeService.Deleted += DataTypeServiceDeleted; - DataTypeService.Saved += DataTypeServiceSaved - //END FIX THIS - + // bind to user and user type events - Bind(() => UserService.SavedUserType += UserService_SavedUserType, - () => UserService.SavedUserType -= UserService_SavedUserType); - Bind(() => UserService.DeletedUserType += UserService_DeletedUserType, - () => UserService.DeletedUserType -= UserService_DeletedUserType); + Bind(() => UserService.SavedUserGroup += UserServiceSavedUserGroup, + () => UserService.SavedUserGroup -= UserServiceSavedUserGroup); + Bind(() => UserService.DeletedUserGroup += UserServiceDeletedUserGroup, + () => UserService.DeletedUserGroup -= UserServiceDeletedUserGroup); Bind(() => UserService.SavedUser += UserService_SavedUser, () => UserService.SavedUser -= UserService_SavedUser); Bind(() => UserService.DeletedUser += UserService_DeletedUser, diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8c22fec893..e8c92893fa 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -443,6 +443,8 @@ + ASPXCodeBehind + EditUserGroup.aspx diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index 7331cdb6f5..de60960e5a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -25,6 +25,7 @@ using Umbraco.Core.Models; using PropertyType = umbraco.cms.businesslogic.propertytype.PropertyType; using System.Text.RegularExpressions; using System.Text; +using Umbraco.Core.Security; namespace umbraco.cms.presentation.user { @@ -62,7 +63,7 @@ namespace umbraco.cms.presentation.user protected DropDownList cExcerpt = new DropDownList(); protected ContentPicker cMediaPicker = new ContentPicker(); protected ContentPicker cContentPicker = new ContentPicker(); - protected CustomValidator sectionValidator = new CustomValidator(); + //protected CustomValidator sectionValidator = new CustomValidator(); protected UpdatePanel pnlGroups = new UpdatePanel(); protected PlaceHolder pnlGroupControls = new PlaceHolder(); @@ -294,6 +295,17 @@ namespace umbraco.cms.presentation.user ClientTools .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) .SyncTree(UID.ToString(), IsPostBack); + } + + private void LnameCustomValidator_OnServerValidate(object source, ServerValidateEventArgs args) + { + var usersWithLoginName = ApplicationContext.Services.UserService.GetByUsername(lname.Text); + args.IsValid = usersWithLoginName == null || usersWithLoginName.Id == u.Id; + } + + private void EmailCustomValidator_OnServerValidate(object source, ServerValidateEventArgs args) + { + args.IsValid = MembershipProviderBase.IsEmailValid(email.Text.Trim()); } private void BindGroups() From 0bbef8cf949558ee2fcf6bfd74533a69a91fb2b7 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 4 May 2017 11:22:04 +0200 Subject: [PATCH 034/510] add temp resource for hardcoded data --- .../src/common/resources/users.resource.js | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js new file mode 100644 index 0000000000..9b7ffb6750 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -0,0 +1,101 @@ +/** + * @ngdoc service + * @name umbraco.resources.usersResource + * @function + * + * @description + * Used by the users section to get users and send requests to create, invite, delete, etc. users. + */ +(function () { + 'use strict'; + + function usersResource($http, umbRequestHelper, $q) { + + function getUsers() { + var deferred = $q.defer(); + var users = [ + { + "name": "Tammy Contreras", + "userGroupName": "Admin", + "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg", + "state": "active" + }, + { + "name": "Edward Flores", + "userGroupName": "Admin", + "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/marcosmoralez/128.jpg", + "state": "active" + }, + { + "name": "Benjamin Mills", + "userGroupName": "Writer", + "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/dancounsell/128.jpg", + "state": "disabled" + }, + { + "name": "Samantha Martinez", + "userGroupName": "Editor", + "avatar": "", + "state": "pending" + }, + { + "name": "Angela Stone", + "userGroupName": "Editor", + "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", + "state": "active" + }, + { + "name": "Beverly Silva", + "userGroupName": "Editor", + "avatar": "", + "state": "active" + }, + { + "name": "Arthur Welch", + "userGroupName": "Editor", + "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/ashleyford/128.jpg", + "state": "active" + }, + { + "name": "Ruth Turner", + "userGroupName": "Translator", + "avatar": "", + "state": "pending" + } + ]; + deferred.resolve(users); + return deferred.promise; + } + + function getUserGroups() { + var deferred = $q.defer(); + var userGroups = [ + { + "name": "Admin" + }, + { + "name": "Writer" + }, + { + "name": "Editor" + }, + { + "name": "Translator" + } + ]; + deferred.resolve(userGroups); + return deferred.promise; + } + + var resource = { + getUsers: getUsers, + getUserGroups: getUserGroups + }; + + return resource; + + } + + angular.module('umbraco.resources').factory('usersResource', usersResource); + +})(); From c8813c8584a5e8c43d38d5d17a1aaa149ca39b4f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 4 May 2017 11:23:38 +0200 Subject: [PATCH 035/510] add id to user groups data --- .../src/common/resources/users.resource.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 9b7ffb6750..a9ac7d5b8d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -71,16 +71,20 @@ var deferred = $q.defer(); var userGroups = [ { - "name": "Admin" + "name": "Admin", + "id": 1 }, { - "name": "Writer" + "name": "Writer", + "id": 2 }, { - "name": "Editor" + "name": "Editor", + "id": 3 }, { - "name": "Translator" + "name": "Translator", + "id": 4 } ]; deferred.resolve(userGroups); From e327a971c26a23cd8fac67407ac4e871067a3d61 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 4 May 2017 11:25:20 +0200 Subject: [PATCH 036/510] update umb-avatar component include name and colors --- .../components/umbavatar.directive.js | 25 ++++++- .../src/less/components/umb-avatar.less | 68 +++++++++++++++++-- .../src/views/components/umb-avatar.html | 8 ++- 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js index ba34a752ed..51c8d660db 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js @@ -52,15 +52,38 @@ Use this directive to render an avatar. function AvatarDirective() { + function link(scope, element, attrs, ctrl) { + + scope.initials = ""; + + function onInit() { + scope.initials = getNameInitials(scope.name); + } + + function getNameInitials(name) { + if(name) { + var initials = name.match(/\b\w/g) || []; + initials = ((initials.shift() || '') + (initials.pop() || '')).toUpperCase(); + return initials; + } + } + + onInit(); + + } + var directive = { restrict: 'E', replace: true, templateUrl: 'views/components/umb-avatar.html', scope: { size: "@", + name: "@", + color: "@", imgSrc: "@", imgSrcset: "@" - } + }, + link: link }; return directive; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less index c4414c2880..61441f16b0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less @@ -2,29 +2,87 @@ border-radius: 50%; width: 50px; height: 50px; + background-color: transparent; + display: flex; + align-items: center; + justify-content: center; + color: @black; + font-weight: bold; + font-size: 16px; + box-sizing: border-box; } -.umb-avatar.-xs { +/* Sizes */ + +.umb-avatar--xxs { + width: 26px; + height: 26px; + font-size: 12px; +} + +.umb-avatar--xs { width: 30px; height: 30px; + font-size: 12px; } -.umb-avatar.-s { +.umb-avatar--s { width: 40px; height: 40px; + font-size: 14px; } -.umb-avatar.-m { +.umb-avatar--m { width: 50px; height: 50px; + font-size: 16px; } -.umb-avatar.-l { +.umb-avatar--l { width: 70px; height: 70px; + font-size: 18px; } -.umb-avatar.-xl { +.umb-avatar--xl { width: 100px; height: 100px; + font-size: 20px; +} + +/* Colors */ + +.umb-avatar--white { + background-color: @white; + color: @black; +} + +.umb-avatar--gray { + background-color: @gray-10; + color: @black; +} + +.umb-avatar--primary { + background-color: @turquoise-l1; + color: @white; +} + +.umb-avatar--secondary { + background-color: @purple-l1; + color: @white; +} + +.umb-avatar--success { + background-color: @green-l1; + color: @white; +} + +.umb-avatar--warning { + background-color: @yellow-l1; + color: @white; +} + +.umb-avatar--danger { + background-color: @red-l1; + color: @white; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-avatar.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-avatar.html index 1d23769db2..c589eabcf7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-avatar.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-avatar.html @@ -1 +1,7 @@ - +
        + +
        + {{ initials }} + ? +
        +
        \ No newline at end of file From 7001e791f791505b9d79e0241e76df72955ebe3e Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 4 May 2017 11:29:36 +0200 Subject: [PATCH 037/510] add umb-badge component --- .../components/umbbadge.directive.js | 23 +++++++ src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../src/less/components/umb-badge.less | 65 +++++++++++++++++++ .../src/views/components/umb-badge.html | 1 + 4 files changed, 90 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/umbbadge.directive.js create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/umb-badge.html diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbbadge.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbbadge.directive.js new file mode 100644 index 0000000000..ee2c53eba0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbbadge.directive.js @@ -0,0 +1,23 @@ +(function() { + 'use strict'; + + function BadgeDirective() { + + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-badge.html', + scope: { + size: "@?", + color: "@?" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbBadge', BadgeDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index cfe82ff3ea..7367e9cffd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -120,6 +120,7 @@ @import "components/umb-querybuilder.less"; @import "components/umb-pagination.less"; @import "components/umb-mini-list-view.less"; +@import "components/umb-badge.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less new file mode 100644 index 0000000000..152eedc83f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less @@ -0,0 +1,65 @@ +.umb-badge { + padding: 6px 8px; + margin: 0 5px 0 0; + font-weight: 600; + color: @black; + background-color: @turquoise-washed; + border-width: 1px; + border-style: solid; + border-color: @turquoise; + display: inline-flex; + align-items: center; + justify-content: center; +} + +// Colors +.umb-badge--primary { + background-color: @turquoise-washed; + border-color: @turquoise; +} + +.umb-badge--seconday { + background-color: @purple-washed; + border-color: @purple; +} + +.umb-badge--danger { + background-color: @red-washed; + border-color: @red; +} + +.umb-badge--warning { + background-color: @yellow-washed; + border-color: @yellow; +} + +.umb-badge--success { + background-color: @green-washed; + border-color: @green; +} + +// Size +.umb-badge--xs { + font-size: 13px; + padding: 1px 6px; +} + +.umb-badge--s { + font-size: 14px; + padding: 3px 6px; +} + +.umb-badge--m { + font-size: 16px; + padding: 6px 8px; +} + +.umb-badge--l { + font-size: 18px; + padding: 6px 8px; +} + +.umb-badge--xl { + font-size: 20px; + padding: 6px 8px; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-badge.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-badge.html new file mode 100644 index 0000000000..1fd0c91520 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-badge.html @@ -0,0 +1 @@ + \ No newline at end of file From e7137e9cce56f6256d4eb1b6165cc8ad3816c9f3 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 4 May 2017 11:34:09 +0200 Subject: [PATCH 038/510] first steps on user dashboard --- src/Umbraco.Web.UI.Client/src/less/belle.less | 2 + .../src/less/components/users/umb-users.less | 38 +++++ src/Umbraco.Web.UI.Client/src/less/main.less | 11 ++ .../components/html/umb-control-group.html | 6 +- .../src/views/usersV2/overview.controller.js | 64 +++++++++ .../src/views/usersV2/overview.html | 134 ++++++++++++++++++ 6 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/users/umb-users.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/usersV2/overview.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/usersV2/overview.html diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 7367e9cffd..de67e6d00f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -131,6 +131,8 @@ @import "components/umb-node-preview.less"; @import "components/umb-mini-editor.less"; +@import "components/users/umb-users.less"; + // Utilities @import "utilities/_flexbox.less"; @import "utilities/_spacing.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-users.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-users.less new file mode 100644 index 0000000000..d65b3e7aae --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-users.less @@ -0,0 +1,38 @@ +.umb-users { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.umb-user { + flex: 1 1 200px; + background-color: @gray-10; + border: 1px solid @gray-9; + border-radius: 3px; + padding: 15px; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin: 10px; + cursor: pointer; +} + +.umb-user:hover { + border-color: @turquoise; +} + +.umb-user__avatar { + margin-bottom: 15px; +} + +.umb-user__name { + text-align: center; + font-size: 16px; + font-weight: bold; +} + +.umb-user__group { + +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 1b573bbe0b..32426ff263 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -113,6 +113,17 @@ h5.-black { border: none; } +/* BLOCK MODE */ +.block-form .umb-control-group { + border-bottom: none; + margin-bottom: 10px !important; + padding-bottom: 0; +} + +.block-form .umb-control-group label .help-block, +.block-form .umb-control-group label small { + font-size: 13px; +} /*COMPACT MODE */ .compact .umb-pane{margin: 0px 0px 15px 0px;} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html index b07c2b9c72..c16cf91834 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html @@ -3,11 +3,9 @@
        - -
        -
        +
        \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.controller.js new file mode 100644 index 0000000000..9a4e4c1f5b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.controller.js @@ -0,0 +1,64 @@ +(function () { + "use strict"; + + function UsersOverviewController($scope, $timeout, usersResource) { + + var vm = this; + + vm.page = {}; + vm.page.name = "Users"; + vm.users = []; + vm.userGroups = []; + vm.usersViewState = 'overview'; + + vm.save = save; + vm.setUsersViewState = setUsersViewState; + vm.getUserStateType = getUserStateType; + + function init() { + + vm.loading = true; + + // Get users + usersResource.getUsers().then(function (users) { + vm.users = users; + }); + + // Get user groups + usersResource.getUserGroups().then(function (userGroups) { + vm.userGroups = userGroups; + }); + + // fake loading + $timeout(function () { + vm.loading = false; + }, 500); + + } + + function save() { + alert("save"); + } + + function setUsersViewState(state) { + vm.usersViewState = state; + } + + function getUserStateType(state) { + switch (state) { + case "disabled" || "umbracoDisabled": + return "danger"; + case "pending": + return "warning"; + default: + return "success"; + } + } + + init(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.Users.OverviewController", UsersOverviewController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.html b/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.html new file mode 100644 index 0000000000..a180cfdc82 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.html @@ -0,0 +1,134 @@ +
        + + + +
        + + + + + + + + + + + +
        + + + + + + + +
        +
        + + +
        {{user.name}}
        +
        {{user.userGroupName}}
        + +
        +
        +
        + + +
        + + + + ← Take me back + + + +
        + +
        +

        Invite user

        +

        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non libero vel turpis ultrices pharetra.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        +
        + +
        + +
        + +
        + +
        + +
        From 971f92301f3e3355682c5f472ce3e474c2be148d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 4 May 2017 11:45:58 +0200 Subject: [PATCH 039/510] add pagination to users --- .../src/views/usersV2/overview.controller.js | 4 ++++ .../src/views/usersV2/overview.html | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.controller.js index 9a4e4c1f5b..43bc252e8a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.controller.js @@ -10,6 +10,10 @@ vm.users = []; vm.userGroups = []; vm.usersViewState = 'overview'; + vm.usersPagination = { + "pageNumber": 1, + "totalPages": 5 + } vm.save = save; vm.setUsersViewState = setUsersViewState; diff --git a/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.html b/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.html index a180cfdc82..9f26f377d5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/usersV2/overview.html @@ -62,6 +62,15 @@ --> + +
        + + +
        + From 1a29a3237d3cfb3254d64164d4db85f5f489bdc3 Mon Sep 17 00:00:00 2001 From: Chriztian Steinmeier Date: Fri, 5 May 2017 23:20:37 +0200 Subject: [PATCH 040/510] Add files from Douglas Robar --- .../Templates/Breadcrumb.cshtml | 4 +-- .../Templates/Gallery.cshtml | 28 +++++++++---------- .../ListAncestorsFromCurrentPage.cshtml | 6 ++-- .../ListChildPagesFromChangeableSource.cshtml | 9 +++--- .../ListChildPagesFromCurrentPage.cshtml | 10 ++++++- .../ListChildPagesOrderedByDate.cshtml | 27 ++++++++++++------ .../ListChildPagesOrderedByName.cshtml | 27 ++++++++++++------ .../ListChildPagesOrderedByProperty.cshtml | 17 ++++++----- .../ListChildPagesWithDoctype.cshtml | 12 +++----- .../ListDescendantsFromCurrentPage.cshtml | 12 ++++---- .../ListImagesFromMediaFolder.cshtml | 6 ++-- .../Templates/MultinodeTree-picker.cshtml | 22 ++++++++------- .../Templates/Navigation.cshtml | 21 ++++++++------ .../Templates/SiteMap.cshtml | 8 +++--- 14 files changed, 121 insertions(+), 88 deletions(-) mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml mode change 100644 => 100755 src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml old mode 100644 new mode 100755 index ff63081996..2f8b479eae --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml @@ -7,7 +7,7 @@ - Finally it outputs the name of the current page (without a link) *@ -@{ var selection = CurrentPage.Ancestors(); } +@{ var selection = Model.Content.Ancestors(); } @if (selection.Any()) { @@ -19,6 +19,6 @@ } @* Display the current page as the last item in the list *@ -
      • @CurrentPage.Name
      • +
      • @Model.Content.Name
      } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml old mode 100644 new mode 100755 index f7c5954c5a..fe8254d37c --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml @@ -13,38 +13,38 @@ Type: (note: you can use a Single Media Picker if that's more appropriate to your needs) *@ -@{ var mediaIds = Model.MacroParameters["mediaIds"]; } +@{ var mediaIds = Model.MacroParameters["mediaIds"] as string; } + @if (mediaIds != null) { -
        - @foreach (var mediaId in mediaIds.ToString().Split(',')) +
        + @foreach (var mediaId in mediaIds.Split(',')) { - var media = Umbraco.Media(mediaId); + var media = Umbraco.TypedMedia(mediaId); @* a single image *@ if (media.DocumentTypeAlias == "Image") { - @Render(media); + @Render(media as Image); } @* a folder with images under it *@ - if (media.Children("Image").Any()) + if (media.Children().Any()) { - foreach (var image in media.Children("Image")) + foreach (var image in media.Children()) { @Render(image); } } - } -
      + } -@helper Render(dynamic item) +@helper Render(Image item) { -
    • - - @item.Name +
    • + } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml old mode 100644 new mode 100755 index b7ca9d06e4..646128f351 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml @@ -7,18 +7,18 @@ - Finally it outputs the name of the current page (without a link) *@ -@{ var selection = CurrentPage.Ancestors(); } +@{ var selection = Model.Content.Ancestors(); } @if (selection.Any()) {
        @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@ - @foreach (var item in selection.OrderBy("Level")) + @foreach (var item in selection.OrderBy(x => x.Level)) {
      • @item.Name »
      • } @* Display the current page as the last item in the list *@ -
      • @CurrentPage.Name
      • +
      • @Model.Content.Name
      } diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml old mode 100644 new mode 100755 index 2fa2aab07c..3af1291be4 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml @@ -13,20 +13,19 @@ *@ @{ var startNodeId = Model.MacroParameters["startNodeId"]; } + @if (startNodeId != null) { @* Get the starting page *@ - var startNode = Umbraco.Content(startNodeId); - var selection = startNode.Children.Where("Visible"); + var startNode = Umbraco.TypedContent(startNodeId); + var selection = startNode.Children.Where(x => x.IsVisible()); if (selection.Any()) { } diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml old mode 100644 new mode 100755 index 9bd13f68a2..19e8503749 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml @@ -1,6 +1,14 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage -@{ var selection = CurrentPage.Children.Where("Visible"); } +@* + This snippet makes a list of links to the of children of the current page using an unordered html list. + + How it works: + - It uses the Children method to get all child pages + - It then generates links so the visitor can go to each page +*@ + +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()); } @if (selection.Any()) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml old mode 100644 new mode 100755 index c8cbdf7d32..e70186f9b9 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml @@ -1,11 +1,22 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage -@{ var selection = CurrentPage.Children.Where("Visible").OrderBy("CreateDate desc"); } -@* OrderBy() takes the property to sort by and optionally order desc/asc *@ +@* + This snippet makes a list of links to the of children of the current page using an unordered html list. -
        - @foreach (var item in selection) - { -
      • @item.Name
      • - } -
      + How it works: + - It uses the Children method to get all child pages + - It then uses the OrderByDescending() method, which takes the property to sort. In this case the page's creation date. + - It then generates links so the visitor can go to each page +*@ + +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderByDescending(x => x.CreateDate); } + +@if (selection.Any()) +{ +
        + @foreach (var item in selection) + { +
      • @item.Name
      • + } +
      +} diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml old mode 100644 new mode 100755 index da73ff8164..697432bf83 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml @@ -1,11 +1,22 @@ @inherits Umbraco.Web.Mvc.UmbracoTemplatePage -@{ var selection = CurrentPage.Children.Where("Visible").OrderBy("Name"); } -@* OrderBy() takes the property to sort by *@ +@* + This snippet makes a list of links to the of children of the current page using an unordered html list. -
        - @foreach (var item in selection) - { -
      • @item.Name
      • - } -
      + How it works: + - It uses the Children method to get all child pages + - It then uses the OrderBy() method, which takes the property to sort. In this case, the page's name. + - It then generates links so the visitor can go to each page +*@ + +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderBy(x => x.Name); } + +@if (selection.Any()) +{ +
        + @foreach (var item in selection) + { +
      • @item.Name
      • + } +
      +} diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml old mode 100644 new mode 100755 index 436faf1ef5..e2eeb08204 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml @@ -15,12 +15,15 @@ @{ var propertyAlias = Model.MacroParameters["propertyAlias"]; } @if (propertyAlias != null) { - var selection = CurrentPage.Children.Where("Visible").OrderBy(propertyAlias); + var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderBy(x => x.GetPropertyValue(propertyAlias.ToString())); -
        - @foreach (var item in selection) - { -
      • @item.Name
      • - } -
      + @if (selection.Any()) + { +
        + @foreach (var item in selection) + { +
      • @item.Name
      • + } +
      + } } diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml old mode 100644 new mode 100755 index 4ca6f93c71..9ac71a4fd2 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml @@ -1,17 +1,13 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet shows how simple it is to fetch only children of a certain Document Type using Razor. - Be sure to change "DocumentTypeAlias" below to match your needs, such as "TextPage" or "NewsItems". + This snippet shows how simple it is to fetch only children of a certain Document Type. + + Be sure to change "IPublishedContent" below to match your needs, such as "TextPage" or "NewsItem". (You can find the alias of your Document Type by editing it in the Settings section) *@ -@{ var selection = CurrentPage.Children("DocumentTypeAlias").Where("Visible"); } -@* - As an example of more querying, if you have a true/false property with the alias of shouldBeFeatured: - var selection= CurrentPage.Children("DocumentTypeAlias").Where("shouldBeFeatured == true").Where("Visible"); -*@ - +@{ var selection = Model.Content.Children().Where(x => x.IsVisible()); } @if (selection.Any()) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml old mode 100644 new mode 100755 index 1e2274bcc0..4a5329e03e --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml @@ -5,13 +5,13 @@ the page currently being viewed by the website visitor, displayed as nested unordered html lists. *@ -@{ var selection = CurrentPage.Children.Where("Visible"); } +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()); } @* Ensure that the Current Page has children *@ @if (selection.Any()) { @* Get the first page in the children, where the property umbracoNaviHide is not True *@ - var naviLevel = CurrentPage.FirstChild().Where("Visible").Level; + var naviLevel = Model.Content.FirstChild(x => x.IsVisible()).Level; @* Add in level for a CSS hook *@
        @@ -22,7 +22,7 @@ @item.Name @* if this child page has any children, where the property umbracoNaviHide is not True *@ - @if (item.Children.Where("Visible").Any()) + @if (item.Children.Where(x => x.IsVisible()).Any()) { @* Call our helper to display the children *@ @childPages(item.Children) @@ -33,7 +33,7 @@ } -@helper childPages(dynamic selection) +@helper childPages(IEnumerable selection) { @* Ensure that we have a collection of pages *@ if (selection.Any()) @@ -43,13 +43,13 @@ @* Add in level for a CSS hook *@
          - @foreach (var item in selection.Where("Visible")) + @foreach (var item in selection.Where(x => x.IsVisible())) {
        • @item.Name @* if the this page has any children, where the property umbracoNaviHide is not True *@ - @if (item.Children.Where("Visible").Any()) + @if (item.Children.Where(x => x.IsVisible()).Any()) { @* Call our helper to display the children *@ @childPages(item.Children) diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml old mode 100644 new mode 100755 index 1549c1eed2..9a73d642c9 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml @@ -16,8 +16,8 @@ @if (mediaId != null) { @* Get all the media item associated with the id passed in *@ - var media = Umbraco.Media(mediaId); - var selection = media.Children("Image"); + var media = Umbraco.TypedMedia(mediaId); + var selection = media.Children(); if (selection.Any()) { @@ -25,7 +25,7 @@ @foreach (var item in selection) {
        • - @item.Name + @item.Name
        • }
        diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml old mode 100644 new mode 100755 index ae8e427449..f73ea8ea5f --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml @@ -8,14 +8,16 @@ multinode treepicker (so: replace "PropertyWithPicker" with the alias of your property). *@ -@{ var selection = CurrentPage.PropertyWithPicker.Split(','); } +@{ var selection = Model.Content.GetPropertyValue>("PropertyWithPicker"); } -
          - @foreach (var id in selection) - { - var item = Umbraco.Content(id); -
        • - @item.Name -
        • - } -
        \ No newline at end of file +@if (selection.Any()) +{ +
          + @foreach (var item in selection) + { +
        • + @item.Name +
        • + } +
        +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml old mode 100644 new mode 100755 index f2a4920a12..cc616c48b0 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml @@ -6,13 +6,16 @@ It also highlights the current active page/section in the navigation with the css class "current". *@ -@{ var selection = CurrentPage.Site().Children.Where("Visible"); } +@{ var selection = Model.Content.Site().Children.Where(x => x.IsVisible()); } -
          - @foreach (var item in selection) - { -
        • - @item.Name -
        • - } -
        +@if (selection.Any()) +{ +
          + @foreach (var item in selection) + { +
        • + @item.Name +
        • + } +
        +} diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml old mode 100644 new mode 100755 index 0aef1eb3a4..219837da78 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml @@ -7,7 +7,7 @@ - It uses a custom Razor helper called Traverse() to select and display the markup and links. *@ -@{ var selection = CurrentPage.Site(); } +@{ var selection = Model.Content.Site(); }
        @* Render the sitemap by passing the root node to the traverse helper, below *@ @@ -15,14 +15,14 @@
        -@* Helper method to travers through all descendants *@ -@helper Traverse(dynamic node) +@* Helper method to traverse through all descendants *@ +@helper Traverse(IPublishedContent node) { @* Update the level to reflect how deep you want the sitemap to go *@ var maxLevelForSitemap = 4; @* Select visible children *@ - var selection = node.Children.Where("Visible").Where("Level <= " + maxLevelForSitemap); + var selection = node.Children.Where(x => x.IsVisible() && x.Level <= maxLevelForSitemap); @* If any items are returned, render a list *@ if (selection.Any()) From 75e375ff54804fe0c635bf8d036da203ba9f7f8a Mon Sep 17 00:00:00 2001 From: Chriztian Steinmeier Date: Fri, 5 May 2017 23:55:23 +0200 Subject: [PATCH 041/510] Minor edits to the comments Mainly "Id's" => "Ids" and "html" => "HTML" :eyes: --- .../Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml | 2 +- .../Umbraco/PartialViewMacros/Templates/Gallery.cshtml | 6 +++--- .../Templates/ListAncestorsFromCurrentPage.cshtml | 2 +- .../Templates/ListChildPagesFromCurrentPage.cshtml | 2 +- .../Templates/ListChildPagesOrderedByDate.cshtml | 2 +- .../Templates/ListChildPagesOrderedByName.cshtml | 2 +- .../Templates/ListDescendantsFromCurrentPage.cshtml | 4 ++-- .../Templates/ListImagesFromMediaFolder.cshtml | 4 ++-- .../PartialViewMacros/Templates/MultinodeTree-picker.cshtml | 4 ++-- .../Umbraco/PartialViewMacros/Templates/Navigation.cshtml | 2 +- .../Umbraco/PartialViewMacros/Templates/SiteMap.cshtml | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml index 2f8b479eae..5dd44df7f9 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml @@ -1,6 +1,6 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet makes a breadcrumb of parents using an unordered html list. + This snippet makes a breadcrumb of parents using an unordered HTML list. How it works: - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml index fe8254d37c..7ed0e01680 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml @@ -1,16 +1,16 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - Macro to display a gallery of images from media the media section. + Macro to display a gallery of images from the Media section. Works with either a 'Single Media Picker' or a 'Multiple Media Picker' macro parameter (see below). How it works: - Confirm the macro parameter has been passed in with a value - - Loop through all the media Id's passed in (might be a single item, might be many) + - Loop through all the media Ids passed in (might be a single item, might be many) - Display any individual images, as well as any folders of images Macro Parameters To Create, for this macro to work: Alias:mediaIds Name:Select folders and/or images Type: Multiple Media Picker - Type: (note: you can use a Single Media Picker if that's more appropriate to your needs) + Type: (note: You can use a Single Media Picker if that's more appropriate to your needs) *@ @{ var mediaIds = Model.MacroParameters["mediaIds"] as string; } diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml index 646128f351..8d4d897b89 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml @@ -1,6 +1,6 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet makes a list of links to the of parents of the current page using an unordered html list. + This snippet makes a list of links to the of parents of the current page using an unordered HTML list. How it works: - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml index 19e8503749..09144cff47 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml @@ -1,7 +1,7 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet makes a list of links to the of children of the current page using an unordered html list. + This snippet makes a list of links to the of children of the current page using an unordered HTML list. How it works: - It uses the Children method to get all child pages diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml index e70186f9b9..5cd74a62f9 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml @@ -1,7 +1,7 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet makes a list of links to the of children of the current page using an unordered html list. + This snippet makes a list of links to the of children of the current page using an unordered HTML list. How it works: - It uses the Children method to get all child pages diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml index 697432bf83..643855006f 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml @@ -1,7 +1,7 @@ @inherits Umbraco.Web.Mvc.UmbracoTemplatePage @* - This snippet makes a list of links to the of children of the current page using an unordered html list. + This snippet makes a list of links to the of children of the current page using an unordered HTML list. How it works: - It uses the Children method to get all child pages diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml index 4a5329e03e..e649328cfb 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml @@ -2,7 +2,7 @@ @* This snippet creates links for every single page (no matter how deep) below - the page currently being viewed by the website visitor, displayed as nested unordered html lists. + the page currently being viewed by the website visitor, displayed as nested unordered HTML lists. *@ @{ var selection = Model.Content.Children.Where(x => x.IsVisible()); } @@ -15,7 +15,7 @@ @* Add in level for a CSS hook *@
          - @* For each child page where the property umbracoNaviHide is not True *@ + @* Loop through the selection *@ @foreach (var item in selection) {
        • diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml index 9a73d642c9..6e5d68b829 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml @@ -5,7 +5,7 @@ How it works: - Confirm the macro parameter has been passed in with a value - - Loop through all the media Id's passed in (might be a single item, might be many) + - Loop through all the media Ids passed in (might be a single item, might be many) - Display any individual images, as well as any folders of images Macro Parameters To Create, for this macro to work: @@ -15,7 +15,7 @@ @{ var mediaId = Model.MacroParameters["mediaId"]; } @if (mediaId != null) { - @* Get all the media item associated with the id passed in *@ + @* Get the media item associated with the id passed in *@ var media = Umbraco.TypedMedia(mediaId); var selection = media.Children(); diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml index f73ea8ea5f..379b5b0ea0 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml @@ -1,8 +1,8 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet lists the items from a Multinode tree picker, using the pickers default settings. - Content Values stored as xml. + This snippet lists the items from a Multinode tree picker, using the picker's default settings. + Content Values stored as XML. To get it working with any site's data structure, set the selection equal to the property which has the multinode treepicker (so: replace "PropertyWithPicker" with the alias of your property). diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml index cc616c48b0..94870a37c4 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml @@ -3,7 +3,7 @@ @* This snippet displays a list of links of the pages immediately under the top-most page in the content tree. This is the home page for a standard website. - It also highlights the current active page/section in the navigation with the css class "current". + It also highlights the current active page/section in the navigation with the CSS class "current". *@ @{ var selection = Model.Content.Site().Children.Where(x => x.IsVisible()); } diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml index 219837da78..7630b26ed1 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml @@ -1,7 +1,7 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet makes a list of links of all visible pages of the site, as nested unordered html lists. + This snippet makes a list of links of all visible pages of the site, as nested unordered HTML lists. How it works: - It uses a custom Razor helper called Traverse() to select and display the markup and links. From c0c1e2bc067dd2a2ab67ccab75dfddf4d354d325 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 May 2017 10:52:45 +0200 Subject: [PATCH 042/510] prototype user invite --- .../views/common/dialogs/login.controller.js | 19 +++++- .../src/views/common/dialogs/login.html | 58 ++++++++++++++++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 49db7dc191..106e748c20 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,5 +1,20 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, $cookies, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService) { + function ($scope, $cookies, $location, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService) { + + $scope.isInvite = false; + + function init() { + // Check if it is a new user + if ($location.search().invite) { + $scope.isInvite = true; + $scope.inviteSetPassword = true; + } + } + + $scope.inviteSavePassword = function() { + $scope.inviteSetPassword = false; + $scope.inviteSetAvatar = true; + }; var setFieldFocus = function(form, field) { $timeout(function() { @@ -234,4 +249,6 @@ $scope.showLogin(); } + init(); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 65ce1c3ecb..991b52cc62 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -1,5 +1,5 @@ 
          - +
          @@ -8,7 +8,61 @@
          -
  • [DataMember(Name = "availableSections")] - [ReadOnly(true)] public IDictionary AvailableSections { get; set; } /// diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index f58dfe875a..00e8e9a230 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -31,14 +31,19 @@ namespace Umbraco.Web.Models.Mapping .ForMember( detail => detail.AvailableCultures, opt => opt.MapFrom(user => applicationContext.Services.TextService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName))) + .ForMember( + detail => detail.EmailHash, + opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) - .ForMember(detail => detail.Path, opt => opt.MapFrom(user => "-1," + user.Id)) + .ForMember(detail => detail.Path, opt => opt.MapFrom(user => "-1," + user.Id)) .ForMember(detail => detail.Notifications, opt => opt.Ignore()) .ForMember(detail => detail.Udi, opt => opt.Ignore()) .ForMember(detail => detail.Icon, opt => opt.Ignore()) .ForMember(detail => detail.Trashed, opt => opt.Ignore()) .ForMember(detail => detail.Alias, opt => opt.Ignore()) .ForMember(detail => detail.Trashed, opt => opt.Ignore()) + //TODO: Enable this when we can! + .ForMember(detail => detail.CustomAvatar, opt => opt.Ignore()) .ForMember(detail => detail.AdditionalData, opt => opt.Ignore()); config.CreateMap() From 16c6fc70bffb07033f547c03a8381327ec6722a4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 22 May 2017 21:26:46 +1000 Subject: [PATCH 101/510] updates user resource to use the real endpoint --- .../src/common/resources/users.resource.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 4c1f7fa1a5..33766bb675 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -71,14 +71,16 @@ 'Failed to retrieve users paged result'); } - function getUser() { - var deferred = $q.defer(); - var user = { - "name": "Tammy Contreras", - "email": "tammy@contreras.com" - }; - deferred.resolve(user); - return deferred.promise; + function getUser(userId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "GetById", + { id: id })), + "Failed to retrieve data for user " + id); + } function getUsers() { From 83d26fd91a9c5615c483d0e8159c1e3eb31cfb97 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 22 May 2017 14:25:29 +0200 Subject: [PATCH 102/510] add breadcrumb + back button to editor footer --- .../src/views/users/role.controller.js | 20 ++++- .../src/views/users/role.html | 23 ++++-- .../src/views/users/user.controller.js | 21 +++++- .../src/views/users/user.html | 73 +++++++++++-------- 4 files changed, 93 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/role.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/role.controller.js index b44103e726..12f23d3f7e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/role.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/role.controller.js @@ -9,7 +9,7 @@ vm.page = {}; vm.userRole = {}; - vm.goBack = goBack; + vm.goToPage = goToPage; vm.openSectionPicker = openSectionPicker; vm.openContentPicker = openContentPicker; vm.openMediaPicker = openMediaPicker; @@ -23,6 +23,7 @@ // get user usersResource.getUserRole().then(function (userRole) { vm.userRole = userRole; + makeBreadcrumbs(); }); // fake loading @@ -32,8 +33,8 @@ } - function goBack() { - $location.path("/users/users/overview").search("subview", "roles"); + function goToPage(ancestor) { + $location.path(ancestor.path).search("subview", ancestor.subView); } function openSectionPicker() { @@ -97,6 +98,19 @@ return "success"; } } + + function makeBreadcrumbs() { + vm.breadcrumbs = [ + { + "name": "Groups", + "path": "/users/users/overview", + "subView": "roles" + }, + { + "name": vm.userRole.name + } + ]; + } init(); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/role.html b/src/Umbraco.Web.UI.Client/src/views/users/role.html index 54cc1abebb..d8c79c881e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/role.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/role.html @@ -15,12 +15,6 @@ - - - ← Back to roles - - -
    @@ -138,8 +132,25 @@ + + + + + + + + + + - - - ← Back to users - - -
    @@ -172,40 +166,38 @@
    {{ vm.user.createdBy }}
    -
    +
    +
    + + +
    -
    +
    + + +
    -
    -
    - - -
    - - - -
    @@ -218,8 +210,25 @@ + + + + + + + + + + Date: Mon, 22 May 2017 14:25:57 +0200 Subject: [PATCH 103/510] increase breadcrumb font size --- .../src/less/components/umb-breadcrumbs.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 2cad03a2ab..d25744a108 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -12,7 +12,7 @@ .umb-breadcrumbs__ancestor-link, .umb-breadcrumbs__ancestor-text { - font-size: 12px; + font-size: 13px; color: @gray-3; max-width: 150px; white-space: nowrap; From 782193b17a3fe307ccb9e9cde39a462b687b1edd Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Mon, 22 May 2017 23:17:17 +0100 Subject: [PATCH 104/510] U4-9661 Remove "move" button in member section --- src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs index f744e4d807..70bdbbb2a3 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs @@ -42,7 +42,7 @@ namespace Umbraco.Web.PropertyEditors allowBulkPublish = true, allowBulkUnpublish = true, allowBulkCopy = true, - allowBulkMove = true, + allowBulkMove = false, allowBulkDelete = true }} }; From 86e4acef86614cfa6a2deeeec48046add2937318 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 May 2017 12:13:01 +1000 Subject: [PATCH 105/510] passes through the user id for the user editor --- .../src/common/resources/users.resource.js | 4 ++-- src/Umbraco.Web.UI.Client/src/views/users/user.controller.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 33766bb675..e460341c77 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -78,8 +78,8 @@ umbRequestHelper.getApiUrl( "userApiBaseUrl", "GetById", - { id: id })), - "Failed to retrieve data for user " + id); + { id: userId })), + "Failed to retrieve data for user " + userId); } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index fd3950bde6..1ac45d2971 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function UserEditController($scope, $timeout, $location, usersResource) { + function UserEditController($scope, $timeout, $location, usersResource, $routeParams) { var vm = this; @@ -16,7 +16,7 @@ vm.loading = true; // get user - usersResource.getUser().then(function (user) { + usersResource.getUser($routeParams.id).then(function (user) { vm.user = user; }); From 1be3f06bc49b95039a65eb75c8748c4b00964687 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 May 2017 12:28:41 +1000 Subject: [PATCH 106/510] Hides the user invite button if there is no email server configured --- .../views/users/views/users/users.controller.js | 13 ++++++++----- src/Umbraco.Web/Editors/BackOfficeController.cs | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 3c8400e2b0..10db1fb908 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -37,12 +37,15 @@ "selected": true }; - vm.defaultButton = { - labelKey:"users_inviteUser", - handler: function() { - vm.setUsersViewState('inviteUser'); + //don't set this if no email is configured + if (Umbraco.Sys.ServerVariables.umbracoSettings.emailServerConfigured) { + vm.defaultButton = { + labelKey: "users_inviteUser", + handler: function () { + vm.setUsersViewState('inviteUser'); } - }; + }; + } vm.subButtons = [ { diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index bb37b22235..e75d7fc321 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Net.Configuration; using System.Net.Http; using System.Text; using System.Threading; @@ -400,6 +401,7 @@ namespace Umbraco.Web.Editors {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')}, {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, + {"emailServerConfigured", HasSmtpServerConfigured()}, } }, { @@ -447,6 +449,18 @@ namespace Umbraco.Web.Editors return JavaScript(result); } + private bool HasSmtpServerConfigured() + { + var config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Request.ApplicationPath); + var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); + if (settings == null || settings.Smtp == null) return false; + if (settings.Smtp.SpecifiedPickupDirectory != null && string.IsNullOrEmpty(settings.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation) == false) + return true; + if (settings.Smtp.Network != null && string.IsNullOrEmpty(settings.Smtp.Network.Host) == false) + return true; + return false; + } + [HttpPost] public ActionResult ExternalLogin(string provider, string redirectUrl = null) { From 6e9e2b99a4e3d312ddc1b8ab71f1b86dd3971c28 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 May 2017 13:18:29 +1000 Subject: [PATCH 107/510] U4-9944 User table needs to have created/update dates , adds more methods to the user controller for enabling/disabling users --- src/Umbraco.Core/Models/Rdbms/UserDto.cs | 11 +++++ src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs | 14 +++++- .../Persistence/Factories/UserFactory.cs | 14 +++--- .../Persistence/Factories/UserGroupFactory.cs | 4 ++ .../Persistence/Mappers/UserMapper.cs | 5 ++ .../Migrations/Initial/BaseDataCreation.cs | 10 ++-- .../AddUserGroupTables.cs | 22 ++++++--- .../UpdateUserTables.cs | 31 +++++++++++++ .../DataTypeDefinitionRepository.cs | 2 +- .../Repositories/UserGroupRepository.cs | 4 ++ .../Repositories/UserRepository.cs | 15 +++--- src/Umbraco.Core/Services/IUserService.cs | 9 +++- src/Umbraco.Core/Services/UserService.cs | 19 +++++--- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../ControllerTesting/TestRunner.cs | 2 +- .../Editors/BackOfficeController.cs | 2 +- src/Umbraco.Web/Editors/UsersController.cs | 46 +++++++++++++++---- 17 files changed, 167 insertions(+), 44 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Models/Rdbms/UserDto.cs index 11624e25f5..5d2257d01b 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserDto.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -75,6 +76,16 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] public DateTime? LastLoginDate { get; set; } + [Column("createDate")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime CreateDate { get; set; } + + [Column("updateDate")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime UpdateDate { get; set; } + [ResultColumn] public List UserGroupDtos { get; set; } } diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs index cadf7cbcdb..cd700dee68 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -33,6 +35,16 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] public string DefaultPermissions { get; set; } + [Column("createDate")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime CreateDate { get; set; } + + [Column("updateDate")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime UpdateDate { get; set; } + [ResultColumn] public List UserGroup2AppDtos { get; set; } } diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index ea30773757..81a4b88289 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -6,10 +6,8 @@ using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Factories { - internal class UserFactory + internal static class UserFactory { - #region Implementation of IEntityFactory - public static IUser BuildEntity(UserDto dto) { var guidId = dto.Id.ToGuid(); @@ -43,6 +41,8 @@ namespace Umbraco.Core.Persistence.Factories user.LastLockoutDate = dto.LastLockoutDate ?? DateTime.MinValue; user.LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue; user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue; + user.CreateDate = dto.CreateDate; + user.UpdateDate = dto.UpdateDate; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -56,7 +56,7 @@ namespace Umbraco.Core.Persistence.Factories } } - public UserDto BuildDto(IUser entity) + public static UserDto BuildDto(IUser entity) { var dto = new UserDto { @@ -74,6 +74,8 @@ namespace Umbraco.Core.Persistence.Factories LastLockoutDate = entity.LastLockoutDate == DateTime.MinValue ? (DateTime?)null : entity.LastLockoutDate, LastLoginDate = entity.LastLoginDate == DateTime.MinValue ? (DateTime?)null : entity.LastLoginDate, LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, + CreateDate = entity.CreateDate, + UpdateDate = entity.UpdateDate }; if (entity.HasIdentity) @@ -82,8 +84,6 @@ namespace Umbraco.Core.Persistence.Factories } return dto; - } - - #endregion + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs index 0ca2f6cb8c..cdbc153814 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs @@ -22,6 +22,8 @@ namespace Umbraco.Core.Persistence.Factories userGroup.Permissions = dto.DefaultPermissions.IsNullOrWhiteSpace() ? Enumerable.Empty() : dto.DefaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); + userGroup.CreateDate = dto.CreateDate; + userGroup.UpdateDate = dto.UpdateDate; if (dto.UserGroup2AppDtos != null) { @@ -48,6 +50,8 @@ namespace Umbraco.Core.Persistence.Factories DefaultPermissions = entity.Permissions == null ? "" : string.Join("", entity.Permissions), Name = entity.Name, UserGroup2AppDtos = new List(), + CreateDate = entity.CreateDate, + UpdateDate = entity.UpdateDate }; foreach (var app in entity.AllowedSections) diff --git a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs index bf7ca9ed8f..9ea69abbf6 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs @@ -40,6 +40,11 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.IsApproved, dto => dto.Disabled); CacheMap(src => src.IsLockedOut, dto => dto.NoConsole); CacheMap(src => src.Language, dto => dto.UserLanguage); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.UpdateDate, dto => dto.UpdateDate); + CacheMap(src => src.LastLockoutDate, dto => dto.LastLockoutDate); + CacheMap(src => src.LastLoginDate, dto => dto.LastLoginDate); + CacheMap(src => src.LastPasswordChangeDate, dto => dto.LastPasswordChangeDate); } #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 90768d2153..434a011a28 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -169,16 +169,16 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateUmbracoUserData() { - _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, ContentStartId = -1, MediaStartId = -1, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en" }); + _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, ContentStartId = -1, MediaStartId = -1, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); //_database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); } private void CreateUmbracoUserGroupData() { - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5F" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, Alias = "translator", Name = "Translators", DefaultPermissions = "AF" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, Alias = "translator", Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); } private void CreateUmbracoUser2UserGroupData() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs index f4b5de6c02..1d2a7a3d9a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero { - [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] + [Migration("7.7.0", 1, Constants.System.UmbracoMigrationName)] public class AddUserGroupTables : MigrationBase { public AddUserGroupTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) @@ -20,33 +20,41 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - AddNewTables(tables); - MigrateUserPermissions(); - MigrateUserTypesToGroups(); - DeleteOldTables(tables, constraints); + if (AddNewTables(tables)) + { + MigrateUserPermissions(); + MigrateUserTypesToGroups(); + DeleteOldTables(tables, constraints); + } } - private void AddNewTables(string[] tables) + private bool AddNewTables(string[] tables) { + var updated = false; if (tables.InvariantContains("umbracoUserGroup") == false) { Create.Table(); + updated = true; } if (tables.InvariantContains("umbracoUser2UserGroup") == false) { Create.Table(); + updated = true; } if (tables.InvariantContains("umbracoUserGroup2App") == false) { - Create.Table(); + Create.Table(); + updated = true; } if (tables.InvariantContains("umbracoUserGroup2NodePermission") == false) { Create.Table(); + updated = true; } + return updated; } private void MigrateUserTypesToGroups() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs new file mode 100644 index 0000000000..04629e74c3 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero +{ + [Migration("7.7.0", 0, Constants.System.UmbracoMigrationName)] + public class UpdateUserTables : MigrationBase + { + public UpdateUserTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) + { + } + + public override void Up() + { + //Don't exeucte if the column is already there + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("createDate")) == false) + Create.Column("createDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); + + if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("updateDate")) == false) + Create.Column("updateDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 6fd4c8f906..8c263f4211 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -208,7 +208,7 @@ AND umbracoNode.id <> @id", throw new DuplicateNameException("A data type with the name " + entity.Name + " already exists"); } - //Updates Modified date and Version Guid + //Updates Modified date ((DataTypeDefinition)entity).UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed diff --git a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs index 58a4e90b5d..727f75f96e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs @@ -226,6 +226,8 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistNewItem(IUserGroup entity) { + ((UserGroup)entity).AddingEntity(); + var userGroupDto = UserGroupFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(userGroupDto)); @@ -236,6 +238,8 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistUpdatedItem(IUserGroup entity) { + ((UserGroup)entity).UpdatingEntity(); + var userGroupDto = UserGroupFactory.BuildDto(entity); Database.Update(userGroupDto); diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 4ad7ecb390..4c203f6dbb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -160,15 +160,15 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistNewItem(IUser entity) { - var userFactory = new UserFactory(); - + ((User)entity).AddingEntity(); + //ensure security stamp if non if (entity.SecurityStamp.IsNullOrWhiteSpace()) { entity.SecurityStamp = Guid.NewGuid().ToString(); } - var userDto = userFactory.BuildDto(entity); + var userDto = UserFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(userDto)); entity.Id = id; @@ -196,15 +196,16 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistUpdatedItem(IUser entity) { - var userFactory = new UserFactory(); - + //Updates Modified date + ((User)entity).UpdatingEntity(); + //ensure security stamp if non if (entity.SecurityStamp.IsNullOrWhiteSpace()) { entity.SecurityStamp = Guid.NewGuid().ToString(); } - var userDto = userFactory.BuildDto(entity); + var userDto = UserFactory.BuildDto(entity); //build list of columns to check for saving - we don't want to save the password if it hasn't changed! //List the columns to save, NOTE: would be nice to not have hard coded strings here but no real good way around that @@ -224,6 +225,8 @@ namespace Umbraco.Core.Persistence.Repositories {"lastPasswordChangeDate", "LastPasswordChangeDate"}, {"lastLoginDate", "LastLoginDate"}, {"failedLoginAttempts", "FailedPasswordAttempts"}, + {"createDate", "CreateDate"}, + {"updateDate", "UpdateDate"} }; //create list of properties that have changed diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index ddaf44e6d9..878fef654d 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -51,7 +51,14 @@ namespace Umbraco.Core.Services /// Id of the user to retrieve /// IUser GetUserById(int id); - + + /// + /// Gets a users by Id + /// + /// Ids of the users to retrieve + /// + IEnumerable GetUsersById(params int[] ids); + /// /// Removes a specific section from all user groups /// diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 156e52f9a8..9bba28ee62 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -196,12 +196,8 @@ namespace Umbraco.Core.Services public void Delete(IUser membershipUser) { //disable - membershipUser.IsApproved = false; - //can't rename if it's going to take up too many chars - if (membershipUser.Username.Length + 9 <= 125) - { - membershipUser.Username = DateTime.Now.ToString("yyyyMMdd") + "_" + membershipUser.Username; - } + membershipUser.IsApproved = false; + Save(membershipUser); } @@ -637,6 +633,17 @@ namespace Umbraco.Core.Services } } + public IEnumerable GetUsersById(params int[] ids) + { + if (ids.Length <= 0) return Enumerable.Empty(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateUserRepository(uow); + return repository.GetAll(ids); + } + } + /// /// Replaces the same permission set for a single group to any number of entities /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 01329ab6eb..b6728affdf 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -510,6 +510,7 @@ + diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs index 12ffda5e04..32e3a08e31 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting { var request = new HttpRequestMessage { - RequestUri = new Uri("http://testserver/"), + RequestUri = new Uri("https://testserver/"), Method = method, }; diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index e75d7fc321..fd70d71c99 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -239,7 +239,7 @@ namespace Umbraco.Web.Editors }, { "userApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.PostDisableUser(0)) + controller => controller.PostSaveUser(null)) }, { "contentApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index e34d98e902..575e354876 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -53,6 +53,16 @@ namespace Umbraco.Web.Editors return Mapper.Map(user); } + /// + /// Returns a paged users collection + /// + /// + /// + /// + /// + /// + /// + /// public PagedResult GetPagedUsers( int pageNumber = 1, int pageSize = 10, @@ -76,6 +86,11 @@ namespace Umbraco.Web.Editors }; } + /// + /// Saves a user + /// + /// + /// public UserDisplay PostSaveUser(UserSave userSave) { if (userSave == null) throw new ArgumentNullException("userSave"); @@ -98,18 +113,33 @@ namespace Umbraco.Web.Editors } /// - /// Disables the user with the given user id + /// Disables the users with the given user ids /// - /// - public bool PostDisableUser([FromUri]int userId) + /// + public bool PostDisableUsers([FromUri]int[] userIds) { - var user = Services.UserService.GetUserById(userId); - if (user == null) + var users = Services.UserService.GetUsersById(userIds); + foreach (var u in users) { - throw new HttpResponseException(HttpStatusCode.NotFound); + //without the permanent flag, this will just disable + Services.UserService.Delete(u); } - //without the permanent flag, this will just disable - Services.UserService.Delete(user); + return true; + } + + /// + /// Enables the users with the given user ids + /// + /// + public bool PostEnableUsers([FromUri]int[] userIds) + { + var users = Services.UserService.GetUsersById(userIds).ToArray(); + foreach (var u in users) + { + u.IsApproved = true; + } + Services.UserService.Save(users); + return true; } } From 45fa106c4281f04cc91cb4815ade6c38f8b421ce Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 May 2017 15:23:10 +1000 Subject: [PATCH 108/510] Adding required user group db columns and models, fixing tests --- src/Umbraco.Core/Models/EntityBase/Entity.cs | 6 +- .../Models/Membership/IUserGroup.cs | 8 +++ .../Models/Membership/UserGroup.cs | 27 +++++++++ src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs | 4 ++ .../Persistence/Factories/UserGroupFactory.cs | 4 +- src/Umbraco.Core/Services/IUserService.cs | 8 --- src/Umbraco.Core/Services/UserService.cs | 34 +---------- .../NotificationsRepositoryTest.cs | 8 +-- .../Services/UserServiceTests.cs | 58 ++++++++++--------- src/Umbraco.Web/Editors/UsersController.cs | 16 ++++- .../Models/ContentEditing/UserDisplay.cs | 4 +- .../Models/ContentEditing/UserGroupDisplay.cs | 29 ++++++++++ .../Models/Mapping/UserModelMapper.cs | 5 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 14 files changed, 132 insertions(+), 80 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index d4da2676c1..3e7770088b 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -119,7 +119,7 @@ namespace Umbraco.Core.Models.EntityBase if (IsPropertyDirty("CreateDate") == false || _createDate == default(DateTime)) CreateDate = DateTime.Now; if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime)) - UpdateDate = CreateDate; + UpdateDate = DateTime.Now; } /// @@ -129,6 +129,10 @@ namespace Umbraco.Core.Models.EntityBase { if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime)) UpdateDate = DateTime.Now; + + //this is just in case + if (_createDate == default(DateTime)) + CreateDate = DateTime.Now; } /// diff --git a/src/Umbraco.Core/Models/Membership/IUserGroup.cs b/src/Umbraco.Core/Models/Membership/IUserGroup.cs index 336caa4c47..d2c58883a2 100644 --- a/src/Umbraco.Core/Models/Membership/IUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IUserGroup.cs @@ -5,6 +5,14 @@ namespace Umbraco.Core.Models.Membership { public interface IUserGroup : IAggregateRoot { + int StartContentId { get; set; } + int StartMediaId { get; set; } + + /// + /// The icon + /// + string Icon { get; set; } + /// /// The alias /// diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index 885a7cbe55..b008f4652f 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -14,7 +14,10 @@ namespace Umbraco.Core.Models.Membership [DataContract(IsReference = true)] internal class UserGroup : Entity, IUserGroup { + private int _startContentId; + private int _startMediaId; private string _alias; + private string _icon; private string _name; private IEnumerable _permissions; private readonly List _sectionCollection; @@ -26,6 +29,9 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); + public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); + public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); + public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); } public UserGroup() @@ -33,6 +39,27 @@ namespace Umbraco.Core.Models.Membership _sectionCollection = new List(); } + [DataMember] + public int StartMediaId + { + get { return _startMediaId; } + set { SetPropertyValueAndDetectChanges(value, ref _startMediaId, Ps.Value.StartMediaIdSelector); } + } + + [DataMember] + public int StartContentId + { + get { return _startContentId; } + set { SetPropertyValueAndDetectChanges(value, ref _startContentId, Ps.Value.StartContentIdSelector); } + } + + [DataMember] + public string Icon + { + get { return _icon; } + set { SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); } + } + [DataMember] public string Alias { diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs index cd700dee68..77f1fed7f1 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs @@ -45,6 +45,10 @@ namespace Umbraco.Core.Models.Rdbms [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime UpdateDate { get; set; } + [Column("icon")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Icon { get; set; } + [ResultColumn] public List UserGroup2AppDtos { get; set; } } diff --git a/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs index cdbc153814..a9cbf95c77 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs @@ -24,6 +24,7 @@ namespace Umbraco.Core.Persistence.Factories : dto.DefaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); userGroup.CreateDate = dto.CreateDate; userGroup.UpdateDate = dto.UpdateDate; + userGroup.Icon = dto.Icon; if (dto.UserGroup2AppDtos != null) { @@ -51,7 +52,8 @@ namespace Umbraco.Core.Persistence.Factories Name = entity.Name, UserGroup2AppDtos = new List(), CreateDate = entity.CreateDate, - UpdateDate = entity.UpdateDate + UpdateDate = entity.UpdateDate, + Icon = entity.Icon }; foreach (var app in entity.AllowedSections) diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 878fef654d..6b798862b0 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -65,14 +65,6 @@ namespace Umbraco.Core.Services /// This is useful when an entire section is removed from config /// Alias of the section to remove void DeleteSectionFromAllUserGroups(string sectionAlias); - - /// - /// Add a specific section to all user groups or those specified as parameters - /// - /// This is useful when a new section is created to allow specific user groups to access it - /// Alias of the section to add - /// Specifiying nothing will add the section to all user - void AddSectionToAllUserGroups(string sectionAlias, params int[] groupIds); /// /// Get permissions set for a user and optional node ids diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 9bba28ee62..8072ecb94d 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -807,39 +807,7 @@ namespace Umbraco.Core.Services uow.Commit(); //TODO: Events? } - } - - /// - /// Add a specific section to all user groups or those specified as parameters - /// - /// This is useful when a new section is created to allow specific user groups to access it - /// Alias of the section to add - /// Specifiying nothing will add the section to all user - public void AddSectionToAllUserGroups(string sectionAlias, params int[] groupIds) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - IEnumerable groups; - if (groupIds.Any()) - { - groups = repository.GetAll(groupIds); - } - else - { - groups = repository.GetAll(); - } - foreach (var group in groups.Where(g => g.AllowedSections.InvariantContains(sectionAlias) == false)) - { - //now add the section for each group and commit - group.AddAllowedSection(sectionAlias); - repository.AddOrUpdate(group); - } - - uow.Commit(); - //TODO: Events? - } - } + } /// /// Get permissions set for a user and node Id diff --git a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs index 79340d71b3..e9d9163b12 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repo = new NotificationsRepository(unitOfWork)) { - var userDto = new UserDto { ContentStartId = -1, Email = "test" , Login = "test" , MediaStartId = -1, Password = "test" , UserName = "test" , UserLanguage = "en" }; + var userDto = new UserDto {ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 10; i++) { - var userDto = new UserDto { ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", UserName = "test" + i, UserLanguage = "en" }; + var userDto = new UserDto {ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); @@ -109,7 +109,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 10; i++) { - var userDto = new UserDto { ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", UserName = "test" + i, UserLanguage = "en" }; + var userDto = new UserDto {ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); @@ -128,7 +128,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repo = new NotificationsRepository(unitOfWork)) { - var userDto = new UserDto { ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", UserName = "test", UserLanguage = "en" }; + var userDto = new UserDto {ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index 682b3695d7..d99f6e70d2 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -40,7 +40,8 @@ namespace Umbraco.Tests.Services { // Arrange var userService = ServiceContext.UserService; - var user = CreateTestUser(); + IUserGroup userGroup; + var user = CreateTestUser(out userGroup); var contentType = MockedContentTypes.CreateSimpleContentType(); ServiceContext.ContentTypeService.Save(contentType); @@ -67,7 +68,8 @@ namespace Umbraco.Tests.Services { // Arrange var userService = ServiceContext.UserService; - var user = CreateTestUser(); + IUserGroup userGroup; + var user = CreateTestUser(out userGroup); var contentType = MockedContentTypes.CreateSimpleContentType(); ServiceContext.ContentTypeService.Save(contentType); @@ -78,12 +80,12 @@ namespace Umbraco.Tests.Services MockedContent.CreateSimpleContent(contentType) }; ServiceContext.ContentService.Save(content); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { 1 }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { 1 }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { 1 }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { 1 }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { 1 }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(2), ActionBrowse.Instance.Letter, new int[] { 1 }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(2), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); // Act var permissions = userService.GetPermissions(user, content.ElementAt(0).Id, content.ElementAt(1).Id, content.ElementAt(2).Id); @@ -476,7 +478,6 @@ namespace Umbraco.Tests.Services { var userGroup = new UserGroup { - Id = 1, Alias = "Group1", Name = "Group 1" }; @@ -521,13 +522,11 @@ namespace Umbraco.Tests.Services { var userGroup1 = new UserGroup { - Id = 1, Alias = "Group1", Name = "Group 1" }; var userGroup2 = new UserGroup { - Id = 2, Alias = "Group2", Name = "Group 2" }; @@ -555,29 +554,27 @@ namespace Umbraco.Tests.Services { var userGroup1 = new UserGroup { - Id = 1, Alias = "Group1", Name = "Group 1" }; + userGroup1.AddAllowedSection("test"); + var userGroup2 = new UserGroup { - Id = 2, Alias = "Group2", Name = "Group 2" }; + userGroup2.AddAllowedSection("test"); + var userGroup3 = new UserGroup - { - Id = 3, + { Alias = "Group3", Name = "Group 3" }; ServiceContext.UserService.Save(userGroup1); ServiceContext.UserService.Save(userGroup2); ServiceContext.UserService.Save(userGroup3); - - //now add the section to specific groups - ServiceContext.UserService.AddSectionToAllUserGroups("test", userGroup1.Id, userGroup2.Id); - + //assert var result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); var result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); @@ -587,7 +584,11 @@ namespace Umbraco.Tests.Services Assert.IsFalse(result3.AllowedSections.Contains("test")); //now add the section to all groups - ServiceContext.UserService.AddSectionToAllUserGroups("test"); + foreach (var userGroup in new[]{userGroup1, userGroup2, userGroup3}) + { + userGroup.AddAllowedSection("test"); + ServiceContext.UserService.Save(userGroup); + } //assert result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); @@ -668,7 +669,8 @@ namespace Umbraco.Tests.Services public void Get_User_By_Username() { // Arrange - var originalUser = CreateTestUser(); + IUserGroup userGroup; + var originalUser = CreateTestUser(out userGroup); // Act @@ -689,9 +691,9 @@ namespace Umbraco.Tests.Services Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); } - private IUser CreateTestUser() + private IUser CreateTestUser(out IUserGroup userGroup) { - var userGroup = CreateTestUserGroup(); + userGroup = CreateTestUserGroup(); var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); user.AddGroup(userGroup.Alias); @@ -703,14 +705,16 @@ namespace Umbraco.Tests.Services { var userGroup = new UserGroup { - Id = 1, Alias = "testGroup", Name = "Test Group", Permissions = "ABCDEFGHIJ1234567".ToCharArray().Select(x => x.ToString()) }; - ServiceContext.UserService.Save(userGroup); - ServiceContext.UserService.AddSectionToAllUserGroups("content", 1); - ServiceContext.UserService.AddSectionToAllUserGroups("media", 1); + + userGroup.AddAllowedSection("content"); + userGroup.AddAllowedSection("media"); + + ServiceContext.UserService.Save(userGroup); + return userGroup; } } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 575e354876..24d1c50a65 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -53,6 +53,15 @@ namespace Umbraco.Web.Editors return Mapper.Map(user); } + /// + /// Returns all user groups + /// + /// + public IEnumerable GetUserGroups() + { + return Mapper.Map< IEnumerable, IEnumerable>(Services.UserService.GetAllUserGroups()); + } + /// /// Returns a paged users collection /// @@ -118,12 +127,13 @@ namespace Umbraco.Web.Editors /// public bool PostDisableUsers([FromUri]int[] userIds) { - var users = Services.UserService.GetUsersById(userIds); + var users = Services.UserService.GetUsersById(userIds).ToArray(); foreach (var u in users) { - //without the permanent flag, this will just disable - Services.UserService.Delete(u); + u.IsApproved = false; } + Services.UserService.Save(users); + return true; } diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs index fe1933669b..a0c2a8ef33 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "email", IsRequired = true)] public string Email { get; set; } - + /// /// The list of group aliases assigned to the user /// @@ -60,9 +60,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "availableCultures")] public IDictionary AvailableCultures { get; set; } + //TODO: This will become StartContentIds as an array! [DataMember(Name = "startContentId")] public int StartContentId { get; set; } + //TODO: This will become StartMediaIds as an array! [DataMember(Name = "startMediaId")] public int StartMediaId { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs new file mode 100644 index 0000000000..06e24b7d38 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "userGroup", Namespace = "")] + public class UserGroupDisplay : EntityBasic, INotificationModel + { + public UserGroupDisplay() + { + Notifications = new List(); + } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + + [DataMember(Name = "sections")] + public IEnumerable Sections { get; set; } + + [DataMember(Name = "startNodeContent")] + public int StartContentId { get; set; } + + [DataMember(Name = "startNodeMedia")] + public int StartMediaId { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 00e8e9a230..885d61bccb 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -16,8 +16,9 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - config.CreateMap() - .ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id)) + config.CreateMap(); + + config.CreateMap() .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups)) .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 43b85365d7..6666189113 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -368,6 +368,7 @@ + From 0cca342b83e67e013959e923abde6220dd20b475 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 May 2017 15:48:08 +1000 Subject: [PATCH 109/510] updates user groups to contain the correct db columns for start nodes, updates mappings, fixes tests. --- src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs | 8 + .../Persistence/Mappers/UserGroupMapper.cs | 4 +- .../Migrations/Initial/BaseDataCreation.cs | 8 +- .../AddUserGroupTables.cs | 9 + .../src/common/resources/users.resource.js | 297 ++++-------------- .../Models/Mapping/UserModelMapper.cs | 9 +- 6 files changed, 88 insertions(+), 247 deletions(-) diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs index 77f1fed7f1..42df2fcf1c 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs @@ -49,6 +49,14 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] public string Icon { get; set; } + [Column("startContentId")] + [NullSetting(NullSetting = NullSettings.Null)] + public int StartContentId { get; set; } + + [Column("startMediaId")] + [NullSetting(NullSetting = NullSettings.Null)] + public int StartMediaId { get; set; } + [ResultColumn] public List UserGroup2AppDtos { get; set; } } diff --git a/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs index 69386063db..cd376e79a9 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs @@ -33,7 +33,9 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.Alias, dto => dto.Alias); CacheMap(src => src.Name, dto => dto.Name); - CacheMap(src => src.Permissions, dto => dto.DefaultPermissions); + CacheMap(src => src.Icon, dto => dto.Icon); + CacheMap(src => src.StartContentId, dto => dto.StartContentId); + CacheMap(src => src.StartMediaId, dto => dto.StartMediaId); } #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 434a011a28..2e0bbf9ad2 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -175,10 +175,10 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateUmbracoUserGroupData() { - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, Alias = "translator", Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, Alias = "translator", Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); } private void CreateUmbracoUser2UserGroupData() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs index 1d2a7a3d9a..5e99a47417 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs @@ -25,9 +25,18 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe MigrateUserPermissions(); MigrateUserTypesToGroups(); DeleteOldTables(tables, constraints); + SetDefaultIcons(); } } + private void SetDefaultIcons() + { + Execute.Sql(string.Format("UPDATE umbracoUserGroup SET icon = '{0}' WHERE userGroupAlias = '{1}'", "", Constants.Security.AdminGroupAlias)); + Execute.Sql(string.Format("UPDATE umbracoUserGroup SET icon = '{0}' WHERE userGroupAlias = '{1}'", "icon-edit", "writer")); + Execute.Sql(string.Format("UPDATE umbracoUserGroup SET icon = '{0}' WHERE userGroupAlias = '{1}'", "icon-tools", "editor")); + Execute.Sql(string.Format("UPDATE umbracoUserGroup SET icon = '{0}' WHERE userGroupAlias = '{1}'", "icon-globe", "translator")); + } + private bool AddNewTables(string[] tables) { var updated = false; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index e460341c77..2c3389479f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -11,17 +11,37 @@ function usersResource($http, umbRequestHelper, $q) { - function disableUser(userId) { - if (!userId) { - throw "userId not specified"; + function disableUsers(userIds) { + if (!userIds) { + throw "userIds not specified"; } + //we need to create a custom query string for the usergroup array, so create it now and we can append the user groups if needed + var qry = "userIds=" + userIds.join("&userIds="); + + return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "userApiBaseUrl", - "PostDisableUser", [{ userId: userId }])), - 'Failed to disable the user ' + userId); + "PostDisableUsers", qry)), + 'Failed to disable the users ' + userIds.join(",")); + } + + function enableUsers(userIds) { + if (!userIds) { + throw "userIds not specified"; + } + + //we need to create a custom query string for the usergroup array, so create it now and we can append the user groups if needed + var qry = "userIds=" + userIds.join("&userIds="); + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostEnableUsers", qry)), + 'Failed to enable the users ' + userIds.join(",")); } function getPagedResults(options) { @@ -48,248 +68,41 @@ options.orderDirection = "Descending"; } - var params = { - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - filter: options.filter - }; - //we need to create a custom query string for the usergroup array, so create it now and we can append the user groups if needed - var qry = umbRequestHelper.dictionaryToQueryString(params); - if (options.userGroups.length > 0) { - //we need to create a custom query string for an array - qry += "&" + options.userGroups.join("&"); - } + var params = { + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: options.filter + }; + //we need to create a custom query string for the usergroup array, so create it now and we can append the user groups if needed + var qry = umbRequestHelper.dictionaryToQueryString(params); + if (options.userGroups.length > 0) { + //we need to create a custom query string for an array + qry += "&userGroups=" + options.userGroups.join("&userGroups="); + } - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "userApiBaseUrl", - "GetPagedUsers", - qry)), - 'Failed to retrieve users paged result'); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "GetPagedUsers", + qry)), + 'Failed to retrieve users paged result'); } function getUser(userId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "userApiBaseUrl", - "GetById", - { id: userId })), - "Failed to retrieve data for user " + userId); - - } + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "GetById", + { id: userId })), + "Failed to retrieve data for user " + userId); - function getUsers() { - var deferred = $q.defer(); - var users = [ - { - "id": 1, - "name": "Tammy Contreras", - "userGroups": [ - { - "name": "Admin" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 2, - "name": "Edward Flores", - "userGroups": [ - { - "name": "Admin" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/marcosmoralez/128.jpg", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 3, - "name": "Benjamin Mills", - "userGroups": [ - { - "name": "Writer" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/dancounsell/128.jpg", - "state": "disabled", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 4, - "name": "Samantha Martinez", - "userGroups": [ - { - "name": "Editor" - } - ], - "avatar": "", - "state": "pending", - "lastLogin": "" - }, - { - "id": 5, - "name": "Angela Stone", - "userGroups": [ - { - "name": "Editor" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 6, - "name": "Beverly Silva", - "userGroups": [ - { - "name": "Editor" - } - ], - "avatar": "", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 7, - "name": "Arthur Welch", - "userGroups": [ - { - "name": "Editor" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/ashleyford/128.jpg", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 8, - "name": "Ruth Turner", - "userGroups": [ - { - "name": "Translator" - }, - { - "name": "Editor" - } - ], - "avatar": "", - "state": "pending", - "lastLogin": "" - }, - { - "id": 9, - "name": "Tammy Contreras", - "userGroups": [ - { - "name": "Translator" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 10, - "name": "Edward Flores", - "userGroups": [ - { - "name": "Admin" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/marcosmoralez/128.jpg", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 11, - "name": "Benjamin Mills", - "userGroups": [ - { - "name": "Writer" - }, - { - "name": "Translator" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/dancounsell/128.jpg", - "state": "disabled", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 12, - "name": "Samantha Martinez", - "userGroupName": "Editor", - "userGroups": [ - { - "name": "Editor" - } - ], - "avatar": "", - "state": "pending", - "lastLogin": "" - }, - { - "id": 13, - "name": "Angela Stone", - "userGroups": [ - { - "name": "Editor" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 14, - "name": "Beverly Silva", - "userGroups": [ - { - "name": "Editor" - } - ], - "avatar": "", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 15, - "name": "Arthur Welch", - "userGroups": [ - { - "name": "Editor" - } - ], - "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/ashleyford/128.jpg", - "state": "active", - "lastLogin": "2014-04-25T01:32:21.196Z" - }, - { - "id": 16, - "name": "Ruth Turner", - "userGroups": [ - { - "name": "Translator" - } - ], - "avatar": "", - "state": "pending", - "lastLogin": "" - } - ]; - deferred.resolve(users); - return deferred.promise; } - + function getUserRole() { var deferred = $q.defer(); var user = { @@ -302,6 +115,7 @@ return deferred.promise; } + //TODO: Change this over to the real resource function getUserGroups() { var deferred = $q.defer(); var userGroups = [ @@ -351,7 +165,8 @@ getUsers: getUsers, getUserRole: getUserRole, getUserGroups: getUserGroups, - disableUser: disableUser, + disableUsers: disableUsers, + enableUsers: enableUsers, getPagedResults: getPagedResults }; diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 885d61bccb..9d1690dd9a 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -16,7 +16,14 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - config.CreateMap(); + config.CreateMap() + .ForMember(detail => detail.Notifications, opt => opt.Ignore()) + .ForMember(detail => detail.Sections, opt => opt.MapFrom(x => x.AllowedSections)) + .ForMember(detail => detail.Udi, opt => opt.Ignore()) + .ForMember(detail => detail.Trashed, opt => opt.Ignore()) + .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) + .ForMember(detail => detail.Path, opt => opt.MapFrom(user => "-1," + user.Id)) + .ForMember(detail => detail.AdditionalData, opt => opt.Ignore()); config.CreateMap() .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups)) From 4c852eab0bcbe831da7bfdbd43474ac4d9b5380c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 23 May 2017 10:16:29 +0200 Subject: [PATCH 110/510] fix js error --- src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 2c3389479f..b14bb0e577 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -162,7 +162,6 @@ var resource = { getUser: getUser, - getUsers: getUsers, getUserRole: getUserRole, getUserGroups: getUserGroups, disableUsers: disableUsers, From 41460d55007d831a7935f47dc96b6fe00a94bc98 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 23 May 2017 11:02:52 +0200 Subject: [PATCH 111/510] rename from role to group --- .../src/common/resources/users.resource.js | 4 +- ...role.controller.js => group.controller.js} | 18 ++++---- .../src/views/users/{role.html => group.html} | 24 +++++----- .../src/views/users/overview.controller.js | 6 +-- .../src/views/users/user.controller.js | 24 +++++----- .../src/views/users/user.html | 28 +++++------ .../groups.controller.js} | 46 +++++++++---------- .../{roles/roles.html => groups/groups.html} | 18 ++++---- 8 files changed, 84 insertions(+), 84 deletions(-) rename src/Umbraco.Web.UI.Client/src/views/users/{role.controller.js => group.controller.js} (85%) rename src/Umbraco.Web.UI.Client/src/views/users/{role.html => group.html} (93%) rename src/Umbraco.Web.UI.Client/src/views/users/views/{roles/roles.controller.js => groups/groups.controller.js} (53%) rename src/Umbraco.Web.UI.Client/src/views/users/views/{roles/roles.html => groups/groups.html} (78%) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index c5d1f7d1db..35f85063e0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -103,7 +103,7 @@ } - function getUserRole() { + function getUserGroup() { var deferred = $q.defer(); var user = { "name": "Admin", @@ -188,7 +188,7 @@ var resource = { getUser: getUser, - getUserRole: getUserRole, + getUserGroup: getUserGroup, getUserGroups: getUserGroups, disableUsers: disableUsers, enableUsers: enableUsers, diff --git a/src/Umbraco.Web.UI.Client/src/views/users/role.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js similarity index 85% rename from src/Umbraco.Web.UI.Client/src/views/users/role.controller.js rename to src/Umbraco.Web.UI.Client/src/views/users/group.controller.js index 12f23d3f7e..ef2f9d378d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/role.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js @@ -1,13 +1,13 @@ (function () { "use strict"; - function UserRoleEditController($scope, $timeout, $location, usersResource) { + function UserGroupEditController($scope, $timeout, $location, usersResource) { var vm = this; vm.loading = false; vm.page = {}; - vm.userRole = {}; + vm.userGroup = {}; vm.goToPage = goToPage; vm.openSectionPicker = openSectionPicker; @@ -21,8 +21,8 @@ vm.loading = true; // get user - usersResource.getUserRole().then(function (userRole) { - vm.userRole = userRole; + usersResource.getUserGroup().then(function (userGroup) { + vm.userGroup = userGroup; makeBreadcrumbs(); }); @@ -49,7 +49,7 @@ show: true, submit: function(model) { if(model.selection) { - vm.userRole.startNodesContent = model.selection; + vm.userGroup.startNodesContent = model.selection; } vm.contentPicker.show = false; vm.contentPicker = null; @@ -72,7 +72,7 @@ show: true, submit: function(model) { if(model.selection) { - vm.userRole.startNodesMedia = model.selection; + vm.userGroup.startNodesMedia = model.selection; } vm.contentPicker.show = false; vm.contentPicker = null; @@ -104,10 +104,10 @@ { "name": "Groups", "path": "/users/users/overview", - "subView": "roles" + "subView": "groups" }, { - "name": vm.userRole.name + "name": vm.userGroup.name } ]; } @@ -116,6 +116,6 @@ } - angular.module("umbraco").controller("Umbraco.Editors.Users.RoleController", UserRoleEditController); + angular.module("umbraco").controller("Umbraco.Editors.Users.GroupController", UserGroupEditController); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/role.html b/src/Umbraco.Web.UI.Client/src/views/users/group.html similarity index 93% rename from src/Umbraco.Web.UI.Client/src/views/users/role.html rename to src/Umbraco.Web.UI.Client/src/views/users/group.html index d8c79c881e..813cb80006 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/role.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.html @@ -1,4 +1,4 @@ -
    +
    @@ -7,8 +7,8 @@ @@ -28,17 +28,17 @@ + on-remove="vm.removeSelectedItem($index, vm.userGroup.sections)"> Add @@ -47,11 +47,11 @@ + on-remove="vm.removeSelectedItem($index, vm.userGroup.startNodesContent)"> + on-remove="vm.removeSelectedItem($index, vm.userGroup.startNodesMedia)"> Users
    + on-remove="vm.removeSelectedUser($index, vm.userGroup.users)"> Permissions
    - + - - + on-remove="vm.removeSelectedItem($index, vm.user.userGroups)"> + Add @@ -248,9 +248,9 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/roles/roles.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js similarity index 53% rename from src/Umbraco.Web.UI.Client/src/views/users/views/roles/roles.controller.js rename to src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js index 6a602e4cfa..4c198b91a8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/roles/roles.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js @@ -1,11 +1,11 @@ (function () { "use strict"; - function UserRolesController($scope, $timeout, $location, usersResource) { + function UserGroupsController($scope, $timeout, $location, usersResource) { var vm = this; - vm.userRoles = []; + vm.userGroups = []; vm.selection = []; vm.viewState = 'overview'; @@ -15,9 +15,9 @@ }; vm.setViewState = setViewState; - vm.goToUserRole = goToUserRole; + vm.goToUserGroup = goToUserGroup; vm.clearSelection = clearSelection; - vm.selectUserRole = selectUserRole; + vm.selectUserGroup = selectUserGroup; vm.selectAll = selectAll; vm.areAllSelected = areAllSelected; @@ -26,8 +26,8 @@ vm.loading = true; // Get users - usersResource.getUserGroups().then(function (userRoles) { - vm.userRoles = userRoles; + usersResource.getUserGroups().then(function (userGroups) { + vm.userGroups = userGroups; }); // fake loading @@ -41,46 +41,46 @@ vm.viewState = state; } - function selectUserRole(userRole, selection) { - if(userRole.selected) { - var index = selection.indexOf(userRole.id); + function selectUserGroup(userGroup, selection) { + if(userGroup.selected) { + var index = selection.indexOf(userGroup.id); selection.splice(index, 1); - userRole.selected = false; + userGroup.selected = false; } else { - userRole.selected = true; - vm.selection.push(userRole.id); + userGroup.selected = true; + vm.selection.push(userGroup.id); } } function clearSelection() { - angular.forEach(vm.userRoles, function(userRole){ - userRole.selected = false; + angular.forEach(vm.userGroups, function(userGroup){ + userGroup.selected = false; }); vm.selection = []; } - function goToUserRole(userRole) { - $location.path('users/users/role/' + userRole.id); + function goToUserGroup(userGroup) { + $location.path('users/users/group/' + userGroup.id); } function selectAll() { if(areAllSelected()) { vm.selection = []; - angular.forEach(vm.userRoles, function(userRole){ - userRole.selected = false; + angular.forEach(vm.userGroups, function(userGroup){ + userGroup.selected = false; }); } else { // clear selection so we don't add the same user twice vm.selection = []; // select all users - angular.forEach(vm.userRoles, function(userRole){ - userRole.selected = true; - vm.selection.push(userRole.id); + angular.forEach(vm.userGroups, function(userGroup){ + userGroup.selected = true; + vm.selection.push(userGroup.id); }); } } function areAllSelected() { - if(vm.selection.length === vm.userRoles.length) { + if(vm.selection.length === vm.userGroups.length) { return true; } } @@ -89,6 +89,6 @@ } - angular.module("umbraco").controller("Umbraco.Editors.Users.RolesController", UserRolesController); + angular.module("umbraco").controller("Umbraco.Editors.Users.GroupsController", UserGroupsController); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/roles/roles.html b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html similarity index 78% rename from src/Umbraco.Web.UI.Client/src/views/users/views/roles/roles.html rename to src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html index b90edb55dc..c7e0c0e504 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/roles/roles.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html @@ -1,4 +1,4 @@ -
    +
    @@ -7,8 +7,8 @@ + action="vm.createUserGroup()" + label="Create Group"> @@ -46,7 +46,7 @@ - {{ vm.selection.length }} of {{ vm.userRoles.length }} selected + {{ vm.selection.length }} of {{ vm.userGroups.length }} selected @@ -56,16 +56,16 @@ button-style="link" label="Delete" icon="icon-trash" - action="vm.deleteUserRole()"> + action="vm.deleteUserGroup()"> -
    - -
    {{userRole.name}}
    - Edit +
    + +
    {{userGroup.name}}
    + Edit
    \ No newline at end of file From f792be949560bcc6cd00605b6ed6e6eb020078cc Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 23 May 2017 15:31:51 +0200 Subject: [PATCH 112/510] wire up language selector --- src/Umbraco.Web.UI.Client/src/views/users/user.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.html b/src/Umbraco.Web.UI.Client/src/views/users/user.html index 8d16bd448e..32c4ea14c0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.html @@ -34,7 +34,7 @@ - From 1a637961b77bb68e9c36e2a80b0089bbe488b535 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 23 May 2017 20:57:43 +0200 Subject: [PATCH 113/510] bump avatar font size --- src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less index 85b597ab91..35409dc470 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less @@ -53,7 +53,7 @@ .umb-avatar--xxl { width: 150px; height: 150px; - font-size: 20px; + font-size: 36px; } /* Colors */ From 38837049f019ad6d8fa9c59d2cee27c0a93e8f27 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 May 2017 19:01:01 +1000 Subject: [PATCH 114/510] adds invite user endpoint with a controller test! --- .../Configuration/GlobalSettings.cs | 24 +++++++- .../Controllers/UsersControllerTests.cs | 59 ++++++++++++++++++- .../TestHelpers/BaseUmbracoApplicationTest.cs | 32 +++++----- .../TestControllerActivatorBase.cs | 4 +- .../ControllerTesting/TestRunner.cs | 7 ++- .../Editors/BackOfficeController.cs | 14 +---- src/Umbraco.Web/Editors/UsersController.cs | 41 +++++++++++++ .../Models/ContentEditing/UserInvite.cs | 26 ++++++++ .../Models/ContentEditing/UserSave.cs | 4 +- .../Models/Mapping/UserModelMapper.cs | 10 ++++ .../Identity/ExternalSignInAutoLinkOptions.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 12 files changed, 186 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/UserInvite.cs diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index acbf0065c0..02f3322ec9 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Configuration; using System.Linq; +using System.Net.Configuration; using System.Web; using System.Web.Configuration; using System.Web.Hosting; @@ -42,7 +43,6 @@ namespace Umbraco.Core.Configuration //ensure the built on (non-changeable) reserved paths are there at all times private const string StaticReservedPaths = "~/app_plugins/,~/install/,"; private const string StaticReservedUrls = "~/config/splashes/booting.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,"; - #endregion /// @@ -53,6 +53,7 @@ namespace Umbraco.Core.Configuration _reservedUrlsCache = null; _reservedPaths = null; _reservedUrls = null; + HasSmtpServer = null; } /// @@ -64,7 +65,26 @@ namespace Umbraco.Core.Configuration ResetInternal(); } - /// + public static bool HasSmtpServerConfigured(string appPath) + { + if (HasSmtpServer.HasValue) return HasSmtpServer.Value; + + var config = WebConfigurationManager.OpenWebConfiguration(appPath); + var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); + if (settings == null || settings.Smtp == null) return false; + if (settings.Smtp.SpecifiedPickupDirectory != null && string.IsNullOrEmpty(settings.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation) == false) + return true; + if (settings.Smtp.Network != null && string.IsNullOrEmpty(settings.Smtp.Network.Host) == false) + return true; + return false; + } + + /// + /// For testing only + /// + internal static bool? HasSmtpServer { get; set; } + + /// /// Gets the reserved urls from web.config. /// /// The reserved urls. diff --git a/src/Umbraco.Tests/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Controllers/UsersControllerTests.cs index 45a4979377..b722bc789b 100644 --- a/src/Umbraco.Tests/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Controllers/UsersControllerTests.cs @@ -32,14 +32,66 @@ using Umbraco.Web.WebApi; namespace Umbraco.Tests.Controllers { [DatabaseTestBehavior(DatabaseBehavior.NoDatabasePerFixture)] - [RequiresAutoMapperMappings] [TestFixture] public class UsersControllerTests : BaseDatabaseFactoryTest { + + [Test] + public async void Invite_User() + { + var runner = new TestRunner((message, helper) => + { + //setup some mocks + Umbraco.Core.Configuration.GlobalSettings.HasSmtpServer = true; + + var userServiceMock = Mock.Get(helper.UmbracoContext.Application.Services.UserService); + + userServiceMock.Setup(service => service.Save(It.IsAny(), It.IsAny())) + .Callback((IUser u, bool raiseEvents) => + { + u.Id = 1234; + }); + userServiceMock.Setup(service => service.GetAllUserGroups(It.IsAny())) + .Returns(Enumerable.Empty); + + //we need to manually apply automapper mappings with the mocked applicationcontext + InitializeMappers(helper.UmbracoContext.Application); + + return new UsersController(helper.UmbracoContext); + }); + + var invite = new UserInvite + { + Id = -1, + Email = "test@test.com", + Message = "Hello test!", + Name = "Test", + UserGroups = new[] {"writers"} + }; + var response = await runner.Execute("Users", "PostInviteUser", HttpMethod.Post, + new ObjectContent(invite, new JsonMediaTypeFormatter())); + + var obj = JsonConvert.DeserializeObject(response.Item2); + + Assert.AreEqual(invite.Name, obj.Name); + Assert.AreEqual(1234, obj.Id); + Assert.AreEqual(invite.Email, obj.Email); + foreach (var group in invite.UserGroups) + { + Assert.IsTrue(obj.UserGroups.Contains(group)); + } + } + [Test] public async void GetPagedUsers_Empty() { - var runner = new TestRunner((message, helper) => new UsersController(helper.UmbracoContext)); + var runner = new TestRunner((message, helper) => + { + //we need to manually apply automapper mappings with the mocked applicationcontext + InitializeMappers(helper.UmbracoContext.Application); + + return new UsersController(helper.UmbracoContext); + }); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); var obj = JsonConvert.DeserializeObject>(response.Item2); @@ -58,6 +110,9 @@ namespace Umbraco.Tests.Controllers userServiceMock.Setup(service => service.GetAll(It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => users); + //we need to manually apply automapper mappings with the mocked applicationcontext + InitializeMappers(helper.UmbracoContext.Application); + return new UsersController(helper.UmbracoContext); }); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 87af8f3234..fa4e851b5e 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -52,7 +52,10 @@ namespace Umbraco.Tests.TestHelpers SetupApplicationContext(); - InitializeMappers(); + if (GetType().GetCustomAttribute(false) != null) + { + InitializeMappers(ApplicationContext); + } FreezeResolution(); @@ -98,24 +101,21 @@ namespace Umbraco.Tests.TestHelpers /// This is an opt-in option because initializing the mappers takes about 500ms which equates to quite a lot /// of time with every test. /// - private void InitializeMappers() + protected virtual void InitializeMappers(ApplicationContext applicationContext) { - if (GetType().GetCustomAttribute(false) != null) + Mapper.Initialize(configuration => { - Mapper.Initialize(configuration => - { - var mappers = PluginManager.Current.FindAndCreateInstances( - specificAssemblies: new[] - { - typeof(ContentModelMapper).Assembly, - typeof(ApplicationRegistrar).Assembly - }); - foreach (var mapper in mappers) + var mappers = PluginManager.Current.FindAndCreateInstances( + specificAssemblies: new[] { - mapper.ConfigureMappings(configuration, ApplicationContext); - } - }); - } + typeof(ContentModelMapper).Assembly, + typeof(ApplicationRegistrar).Assembly + }); + foreach (var mapper in mappers) + { + mapper.ConfigureMappings(configuration, applicationContext); + } + }); } /// diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index bebf6ab1e2..7299d9758e 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -49,7 +49,9 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var serviceContext = new ServiceContext( userService: mockedUserService, - migrationEntryService: mockedMigrationService.Object); + migrationEntryService: mockedMigrationService.Object, + localizedTextService:Mock.Of(), + sectionService:Mock.Of()); //ensure the configuration matches the current version for tests SettingsForTests.ConfigurationStatus = UmbracoVersion.GetSemanticVersion().ToSemanticString(); diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs index 32e3a08e31..cc024b40a2 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting _controllerFactory = controllerFactory; } - public async Task> Execute(string controllerName, string actionName, HttpMethod method) + public async Task> Execute(string controllerName, string actionName, HttpMethod method, HttpContent content = null) { var startup = new TestStartup( configuration => @@ -38,9 +38,12 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var request = new HttpRequestMessage { RequestUri = new Uri("https://testserver/"), - Method = method, + Method = method }; + if (content != null) + request.Content = content; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); Console.WriteLine(request); diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index fd70d71c99..a4260b7a49 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -401,7 +401,7 @@ namespace Umbraco.Web.Editors {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')}, {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, - {"emailServerConfigured", HasSmtpServerConfigured()}, + {"emailServerConfigured", GlobalSettings.HasSmtpServerConfigured(HttpContext.Request.ApplicationPath)}, } }, { @@ -449,17 +449,7 @@ namespace Umbraco.Web.Editors return JavaScript(result); } - private bool HasSmtpServerConfigured() - { - var config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Request.ApplicationPath); - var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); - if (settings == null || settings.Smtp == null) return false; - if (settings.Smtp.SpecifiedPickupDirectory != null && string.IsNullOrEmpty(settings.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation) == false) - return true; - if (settings.Smtp.Network != null && string.IsNullOrEmpty(settings.Smtp.Network.Host) == false) - return true; - return false; - } + [HttpPost] public ActionResult ExternalLogin(string provider, string redirectUrl = null) diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 24d1c50a65..c6ce2ecc81 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -3,15 +3,18 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Web; using System.Web.Http; using AutoMapper; using ClientDependency.Core; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; @@ -95,6 +98,44 @@ namespace Umbraco.Web.Editors }; } + /// + /// Invites a user + /// + /// + /// + /// + /// This will email the user an invite and generate a token that will be validated in the email + /// + public UserDisplay PostInviteUser(UserInvite userSave) + { + if (userSave == null) throw new ArgumentNullException("userSave"); + + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var hasSmtp = GlobalSettings.HasSmtpServerConfigured(RequestContext.VirtualPathRoot); + if (hasSmtp == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse("No Email server is configured")); + } + + var existing = Services.UserService.GetByEmail(userSave.Email); + if (existing != null) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var user = Mapper.Map(userSave); + + Services.UserService.Save(user); + + return Mapper.Map(user); + } + /// /// Saves a user /// diff --git a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs new file mode 100644 index 0000000000..f3bcb80005 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents the data used to invite a user + /// + [DataContract(Name = "user", Namespace = "")] + public class UserInvite : EntityBasic + { + [DataMember(Name = "userGroups")] + [Required] + public IEnumerable UserGroups { get; set; } + + [DataMember(Name = "email", IsRequired = true)] + [Required] + [EmailAddress] + public string Email { get; set; } + + [DataMember(Name = "message", IsRequired = true)] + [Required] + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs index 5fe57e0787..8bef19d62f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs @@ -26,9 +26,9 @@ namespace Umbraco.Web.Models.ContentEditing [EmailAddress] public string Email { get; set; } - [DataMember(Name = "userType")] + [DataMember(Name = "userGroups")] [Required] - public string UserType { get; set; } + public IEnumerable UserGroups { get; set; } [DataMember(Name = "startContentId")] public int StartContentId { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 9d1690dd9a..aa4ad4b6e4 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -16,6 +16,16 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { + config.CreateMap() + .ConstructUsing(invite => new User(invite.Name, invite.Email, invite.Email, Guid.NewGuid().ToString("N"))) + .AfterMap((invite, user) => + { + foreach (var group in invite.UserGroups) + { + user.AddGroup(group); + } + }); + config.CreateMap() .ForMember(detail => detail.Notifications, opt => opt.Ignore()) .ForMember(detail => detail.Sections, opt => opt.MapFrom(x => x.AllowedSections)) diff --git a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs index 832f0b3a30..5b637de3b8 100644 --- a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs @@ -26,6 +26,7 @@ namespace Umbraco.Web.Security.Identity _defaultCulture = defaultCulture ?? GlobalSettings.DefaultUILanguage; } + //TODO: Change this - it will be different when we have user groups! private readonly string _defaultUserType; /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6666189113..6fdc3692fc 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -369,6 +369,7 @@ + From 8b299a070f0d712c4fe143f00183dd97b0fd529b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20St=C3=B6cker?= Date: Wed, 24 May 2017 12:16:46 +0200 Subject: [PATCH 115/510] =?UTF-8?q?Update=20"Element=20hinzuf=C3=BCgen"=20?= =?UTF-8?q?(Add=20grid=20element)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Key no longer used for first element exclusively for ages. New label just hints the (+) as button to add an element (being it the first one or not) -- very generic and thus hopefully future proof. --- src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 776a23183b..70dc22b439 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -859,7 +859,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Element hinzufügen Zeilenlayout auswählen - Einfach auf <i class="icon icon-add blue"></i> klicken, um das erste Element anzulegen + Element mit <i class="icon icon-add blue"></i> hinzufügen Drop content Klicken, um Inhalt einzubetten Klicken, um Abbildung einzufügen From dec9422531927f44e34642b018c07b52fd54d290 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 May 2017 02:03:41 +1000 Subject: [PATCH 116/510] Updated DB and business logic to support multiple starts nodes for a user and for user groups to have start nodes - U4-9915 User Groups will have a start node assigned and Users can have multiple start nodes assigned --- src/Umbraco.Core/Models/ContentType.cs | 10 +-- src/Umbraco.Core/Models/ContentTypeBase.cs | 15 +++-- src/Umbraco.Core/Models/DictionaryItem.cs | 11 ++-- .../Models/Identity/BackOfficeIdentityUser.cs | 8 +-- .../Models/Identity/IdentityModelMappings.cs | 8 +-- .../Models/Membership/IProfile.cs | 12 ++-- src/Umbraco.Core/Models/Membership/IUser.cs | 4 +- src/Umbraco.Core/Models/Membership/User.cs | 61 +++++++++++-------- .../Models/Membership/UserGroup.cs | 11 ++-- .../Models/Membership/UserProfile.cs | 46 ++++++++++++++ src/Umbraco.Core/Models/Property.cs | 50 +++++++-------- src/Umbraco.Core/Models/Rdbms/UserDto.cs | 13 ++-- .../Models/Rdbms/UserStartNodeDto.cs | 36 +++++++++++ src/Umbraco.Core/Models/UserExtensions.cs | 22 +++++-- .../Persistence/Factories/UserFactory.cs | 29 +++++++-- .../Persistence/Mappers/UserMapper.cs | 4 +- .../Migrations/Initial/BaseDataCreation.cs | 3 +- .../Initial/DatabaseSchemaCreation.cs | 1 + .../Initial/DatabaseSchemaResult.cs | 2 +- .../AddUserGroupTables.cs | 8 ++- .../AddUserStartNodeTable.cs | 49 +++++++++++++++ .../Persistence/Relators/UserGroupRelator.cs | 49 ++++++++++----- .../Interfaces/IUserRepository.cs | 3 + .../Repositories/UserRepository.cs | 57 +++++++++++++---- .../BackOfficeClaimsIdentityFactory.cs | 4 +- .../Security/BackOfficeUserStore.cs | 12 ++-- .../Security/UmbracoBackOfficeIdentity.cs | 32 ++++++---- src/Umbraco.Core/Security/UserData.cs | 4 +- src/Umbraco.Core/Services/UserService.cs | 18 +++--- src/Umbraco.Core/Umbraco.Core.csproj | 3 + .../Controllers/UsersControllerTests.cs | 46 ++++++++++++++ .../Models/UserExtensionsTests.cs | 2 +- src/Umbraco.Tests/Models/UserTests.cs | 8 +-- .../NotificationsRepositoryTest.cs | 8 +-- .../Repositories/UserRepositoryTest.cs | 12 ++-- .../UmbracoBackOfficeIdentityTests.cs | 16 ++--- .../Services/UserServiceTests.cs | 5 +- .../AuthenticateEverythingMiddleware.cs | 4 +- .../TestControllerActivatorBase.cs | 4 +- .../TestHelpers/Entities/MockedUser.cs | 2 - .../UmbracoExamine/IndexInitializer.cs | 2 +- .../ContentControllerUnitTests.cs | 26 ++++---- ...terAllowedOutgoingContentAttributeTests.cs | 4 +- .../WebApiEditors/MediaControllerUnitTests.cs | 14 ++--- .../components/tree/umbtree.directive.js | 51 +++++++++------- .../src/common/services/user.service.js | 10 --- .../common/dialogs/linkpicker.controller.js | 18 +++--- .../linkpicker/linkpicker.controller.js | 30 ++++----- .../dashboard/dashboard.tabs.controller.js | 4 +- .../grid/editors/media.controller.js | 2 +- .../grid/editors/rte.controller.js | 2 +- .../mediapicker/mediapicker.controller.js | 2 +- .../propertyeditors/rte/rte.controller.js | 2 +- src/Umbraco.Web/Editors/ContentController.cs | 20 +++--- src/Umbraco.Web/Editors/EntityController.cs | 60 ++++++++++-------- src/Umbraco.Web/Editors/MediaController.cs | 20 +++--- src/Umbraco.Web/Editors/UsersController.cs | 53 +++++++++++++++- .../Models/ContentEditing/UserDetail.cs | 8 +-- .../Models/ContentEditing/UserDisplay.cs | 30 +++------ .../Models/ContentEditing/UserGroupDisplay.cs | 9 ++- .../Models/ContentEditing/UserSave.cs | 8 +-- .../Models/Mapping/UserModelMapper.cs | 47 +++++++++----- src/Umbraco.Web/Security/WebSecurity.cs | 2 - .../Trees/ContentTreeController.cs | 8 +-- .../Trees/ContentTreeControllerBase.cs | 20 +++--- src/Umbraco.Web/Trees/MediaTreeController.cs | 8 +-- .../FilterAllowedOutgoingContentAttribute.cs | 4 +- .../FilterAllowedOutgoingMediaAttribute.cs | 8 +-- .../BasePages/BasePage.cs | 4 +- src/umbraco.businesslogic/User.cs | 27 ++++---- 70 files changed, 774 insertions(+), 421 deletions(-) create mode 100644 src/Umbraco.Core/Models/Membership/UserProfile.cs create mode 100644 src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 88c498a147..81b5272a58 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -54,6 +54,11 @@ namespace Umbraco.Core.Models { public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> TemplateComparer = new DelegateEqualityComparer>( + (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), + templates => templates.GetHashCode()); } /// @@ -91,10 +96,7 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), - templates => templates.GetHashCode())); + Ps.Value.TemplateComparer); } } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 88476f946d..db0bc0e900 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -88,6 +88,12 @@ namespace Umbraco.Core.Models public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> ContentTypeSortComparer = + new DelegateEqualityComparer>( + (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), + sorts => sorts.GetHashCode()); } @@ -254,7 +260,7 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } } - private IDictionary _additionalData; + private readonly IDictionary _additionalData; /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// @@ -273,11 +279,8 @@ namespace Umbraco.Core.Models get { return _allowedContentTypes; } set { - SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), - sorts => sorts.GetHashCode())); + SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, + Ps.Value.ContentTypeSortComparer); } } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 42b047e35b..d5fcc89994 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -37,6 +37,12 @@ namespace Umbraco.Core.Models public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> DictionaryTranslationComparer = + new DelegateEqualityComparer>( + (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), + enumerable => enumerable.GetHashCode()); } /// @@ -79,10 +85,7 @@ namespace Umbraco.Core.Models } SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), - enumerable => enumerable.GetHashCode())); + Ps.Value.DictionaryTranslationComparer); } } } diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 03631bda05..52f56fc2d5 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -14,8 +14,8 @@ namespace Umbraco.Core.Models.Identity public BackOfficeIdentityUser() { - StartMediaId = -1; - StartContentId = -1; + StartMediaIds = new int[] { }; + StartContentIds = new int[] { }; Culture = Configuration.GlobalSettings.DefaultUILanguage; } @@ -31,8 +31,8 @@ namespace Umbraco.Core.Models.Identity /// Gets/sets the user's real name /// public string Name { get; set; } - public int StartContentId { get; set; } - public int StartMediaId { get; set; } + public int[] StartContentIds { get; set; } + public int[] StartMediaIds { get; set; } public string[] AllowedSections { get; set; } public string[] Groups { get; set; } public string Culture { get; set; } diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index 8295ae566d..9d01056f48 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -21,8 +21,8 @@ namespace Umbraco.Core.Models.Identity .ForMember(user => user.PasswordHash, expression => expression.MapFrom(user => GetPasswordHash(user.RawPasswordValue))) .ForMember(user => user.Culture, expression => expression.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember(user => user.Name, expression => expression.MapFrom(user => user.Name)) - .ForMember(user => user.StartMediaId, expression => expression.MapFrom(user => user.StartMediaId)) - .ForMember(user => user.StartContentId, expression => expression.MapFrom(user => user.StartContentId)) + .ForMember(user => user.StartMediaIds, expression => expression.MapFrom(user => user.StartMediaIds)) + .ForMember(user => user.StartContentIds, expression => expression.MapFrom(user => user.StartContentIds)) .ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts)) .ForMember(user => user.Groups, expression => expression.MapFrom(user => user.Groups.ToArray())) .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray())); @@ -33,8 +33,8 @@ namespace Umbraco.Core.Models.Identity .ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections)) .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Groups)) .ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name)) - .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaNodes, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.UserName)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture)) .ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp)); diff --git a/src/Umbraco.Core/Models/Membership/IProfile.cs b/src/Umbraco.Core/Models/Membership/IProfile.cs index 749c3371b8..0e6267a10b 100644 --- a/src/Umbraco.Core/Models/Membership/IProfile.cs +++ b/src/Umbraco.Core/Models/Membership/IProfile.cs @@ -1,15 +1,11 @@ namespace Umbraco.Core.Models.Membership { /// - /// Defines the the Profile interface - /// - /// - /// This interface is pretty useless but has been exposed publicly from 6.x so we're stuck with it. It would make more sense - /// if the Id was an int but since it's not people have to cast it to int all of the time! - /// + /// Defines the the User Profile interface + /// public interface IProfile { - object Id { get; set; } - string Name { get; set; } + int Id { get; } + string Name { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index edb9d9aa7d..0b17fea05b 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -13,8 +13,8 @@ namespace Umbraco.Core.Models.Membership string Name { get; set; } int SessionTimeout { get; set; } - int StartContentId { get; set; } - int StartMediaId { get; set; } + int[] StartContentIds { get; set; } + int[] StartMediaIds { get; set; } string Language { get; set; } /// diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 055b3e439d..f06c373360 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -27,8 +27,8 @@ namespace Umbraco.Core.Models.Membership _language = GlobalSettings.DefaultUILanguage; _isApproved = true; _isLockedOut = false; - _startContentId = -1; - _startMediaId = -1; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; //cannot be null _rawPasswordValue = ""; _allowedSections = new List(); @@ -57,8 +57,8 @@ namespace Umbraco.Core.Models.Membership _userGroups = new List(); _isApproved = true; _isLockedOut = false; - _startContentId = -1; - _startMediaId = -1; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; } /// @@ -88,15 +88,15 @@ namespace Umbraco.Core.Models.Membership _userGroups = new List(userGroups); _isApproved = true; _isLockedOut = false; - _startContentId = -1; - _startMediaId = -1; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; } private string _name; private string _securityStamp; private int _sessionTimeout; - private int _startContentId; - private int _startMediaId; + private int[] _startContentIds; + private int[] _startMediaIds; private int _failedLoginAttempts; private string _username; @@ -124,8 +124,8 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); - public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); - public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); + public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentIds); + public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaIds); public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); @@ -138,6 +138,12 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); public readonly PropertyInfo UserGroupsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Groups); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> IntegerEnumerableComparer = + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); } #region Implementation of IMembershipUser @@ -252,7 +258,7 @@ namespace Umbraco.Core.Models.Membership public IProfile ProfileData { - get { return new UserProfile(this); } + get { return new WrappedUserProfile(this); } } /// @@ -285,10 +291,11 @@ namespace Umbraco.Core.Models.Membership /// The start content id. /// [DataMember] - public int StartContentId + [DoNotClone] + public int[] StartContentIds { - get { return _startContentId; } - set { SetPropertyValueAndDetectChanges(value, ref _startContentId, Ps.Value.StartContentIdSelector); } + get { return _startContentIds; } + set { SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdSelector, Ps.Value.IntegerEnumerableComparer); } } /// @@ -297,11 +304,12 @@ namespace Umbraco.Core.Models.Membership /// /// The start media id. /// - [DataMember] - public int StartMediaId + [DataMember] + [DoNotClone] + public int[] StartMediaIds { - get { return _startMediaId; } - set { SetPropertyValueAndDetectChanges(value, ref _startMediaId, Ps.Value.StartMediaIdSelector); } + get { return _startMediaIds; } + set { SetPropertyValueAndDetectChanges(value, ref _startMediaIds, Ps.Value.StartMediaIdSelector, Ps.Value.IntegerEnumerableComparer); } } [DataMember] @@ -349,7 +357,10 @@ namespace Umbraco.Core.Models.Membership public override object DeepClone() { - var clone = (User)base.DeepClone(); + var clone = (User)base.DeepClone(); + //manually clone the start node props + clone._startContentIds = _startContentIds.ToArray(); + clone._startMediaIds = _startMediaIds.ToArray(); //turn off change tracking clone.DisableChangeTracking(); //need to create new collections otherwise they'll get copied by ref @@ -367,28 +378,26 @@ namespace Umbraco.Core.Models.Membership /// /// Internal class used to wrap the user in a profile /// - private class UserProfile : IProfile + private class WrappedUserProfile : IProfile { private readonly IUser _user; - public UserProfile(IUser user) + public WrappedUserProfile(IUser user) { _user = user; } - public object Id + public int Id { get { return _user.Id; } - set { _user.Id = (int)value; } } public string Name { get { return _user.Name; } - set { _user.Name = value; } } - protected bool Equals(UserProfile other) + private bool Equals(WrappedUserProfile other) { return _user.Equals(other._user); } @@ -398,7 +407,7 @@ namespace Umbraco.Core.Models.Membership if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((UserProfile) obj); + return Equals((WrappedUserProfile) obj); } public override int GetHashCode() diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index b008f4652f..d6674b579d 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -32,6 +32,12 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> StringEnumerableComparer = + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); } public UserGroup() @@ -93,10 +99,7 @@ namespace Umbraco.Core.Models.Membership set { SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode())); + Ps.Value.StringEnumerableComparer); } } diff --git a/src/Umbraco.Core/Models/Membership/UserProfile.cs b/src/Umbraco.Core/Models/Membership/UserProfile.cs new file mode 100644 index 0000000000..d32854ab1b --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/UserProfile.cs @@ -0,0 +1,46 @@ +using System; + +namespace Umbraco.Core.Models.Membership +{ + internal class UserProfile : IProfile, IEquatable + { + public UserProfile(int id, string name) + { + Id = id; + Name = name; + } + + public int Id { get; private set; } + public string Name { get; private set; } + + public bool Equals(UserProfile other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UserProfile) obj); + } + + public override int GetHashCode() + { + return Id; + } + + public static bool operator ==(UserProfile left, UserProfile right) + { + return Equals(left, right); + } + + public static bool operator !=(UserProfile left, UserProfile right) + { + return Equals(left, right) == false; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index dff6a712c7..5df2eaae90 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -49,36 +49,36 @@ namespace Umbraco.Core.Models { public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); - } - private static readonly DelegateEqualityComparer ValueComparer = new DelegateEqualityComparer( - (o, o1) => - { - if (o == null && o1 == null) return true; - - //custom comparer for strings. - if (o is string || o1 is string) + public readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( + (o, o1) => { - //if one is null and another is empty then they are the same - if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) + if (o == null && o1 == null) return true; + + //custom comparer for strings. + if (o is string || o1 is string) { - return true; + //if one is null and another is empty then they are the same + if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) + { + return true; + } + if (o == null || o1 == null) return false; + return o.Equals(o1); } + if (o == null || o1 == null) return false; + + //Custom comparer for enumerable if it is enumerable + var enum1 = o as IEnumerable; + var enum2 = o1 as IEnumerable; + if (enum1 != null && enum2 != null) + { + return enum1.Cast().UnsortedSequenceEqual(enum2.Cast()); + } return o.Equals(o1); - } - - if (o == null || o1 == null) return false; - - //Custom comparer for enumerable if it is enumerable - var enum1 = o as IEnumerable; - var enum2 = o1 as IEnumerable; - if (enum1 != null && enum2 != null) - { - return enum1.Cast().UnsortedSequenceEqual(enum2.Cast()); - } - return o.Equals(o1); - }, o => o.GetHashCode()); + }, o => o.GetHashCode()); + } /// /// Returns the instance of the tag support, by default tags are not enabled @@ -200,7 +200,7 @@ namespace Umbraco.Core.Models } } - SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector, ValueComparer); + SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector, Ps.Value.PropertyValueComparer); } } diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Models/Rdbms/UserDto.cs index 5d2257d01b..0f74a92b7b 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserDto.cs @@ -14,6 +14,7 @@ namespace Umbraco.Core.Models.Rdbms public UserDto() { UserGroupDtos = new List(); + UserStartNodeDtos = new List(); } [Column("id")] @@ -27,14 +28,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("userNoConsole")] [Constraint(Default = "0")] public bool NoConsole { get; set; } - - [Column("startStructureID")] - public int ContentStartId { get; set; } - - [Column("startMediaID")] - [NullSetting(NullSetting = NullSettings.Null)] - public int? MediaStartId { get; set; } - + [Column("userName")] public string UserName { get; set; } @@ -88,5 +82,8 @@ namespace Umbraco.Core.Models.Rdbms [ResultColumn] public List UserGroupDtos { get; set; } + + [ResultColumn] + public List UserStartNodeDtos { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs b/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs new file mode 100644 index 0000000000..e872111829 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs @@ -0,0 +1,36 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoUserStartNode")] + [PrimaryKey("id", autoIncrement = true)] + [ExplicitColumns] + internal class UserStartNodeDto + { + [Column("id")] + [PrimaryKeyColumn(Name = "PK_userStartNode")] + public int Id { get; set; } + + [Column("userId")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [ForeignKey(typeof(UserDto))] + public int UserId { get; set; } + + [Column("startNode")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [ForeignKey(typeof(NodeDto))] + public int StartNode { get; set; } + + [Column("startNodeType")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.UniqueNonClustered, ForColumns = "startNodeType, startNode", Name = "IX_umbracoUserStartNode_startNodeType")] + public int StartNodeType { get; set; } + + public enum StartNodeTypeValue + { + Content = 1, + Media = 2 + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 87b97df602..4894be7b87 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -49,24 +49,34 @@ namespace Umbraco.Core.Models { if (user == null) throw new ArgumentNullException("user"); if (content == null) throw new ArgumentNullException("content"); - return HasPathAccess(content.Path, user.StartContentId, Constants.System.RecycleBinContent); + return HasPathAccess(content.Path, user.StartContentIds, Constants.System.RecycleBinContent); } - internal static bool HasPathAccess(string path, int startNodeId, int recycleBinId) + internal static bool HasPathAccess(string path, int[] startNodeIds, int recycleBinId) { Mandate.ParameterNotNullOrEmpty(path, "path"); var formattedPath = "," + path + ","; - var formattedStartNodeId = "," + startNodeId.ToInvariantString() + ","; var formattedRecycleBinId = "," + recycleBinId.ToInvariantString() + ","; //only users with root access have access to the recycle bin if (formattedPath.Contains(formattedRecycleBinId)) { - return startNodeId == Constants.System.Root; + var hasAccess = startNodeIds.Length == 0 || startNodeIds.Contains(Constants.System.Root); + return hasAccess; + } + + //check for normal paths + foreach (var startNodeId in startNodeIds) + { + var formattedStartNodeId = "," + startNodeId.ToInvariantString() + ","; + + var hasAccess = formattedPath.Contains(formattedStartNodeId); + if (hasAccess) + return true; } - return formattedPath.Contains(formattedStartNodeId); + return false; } /// @@ -79,7 +89,7 @@ namespace Umbraco.Core.Models { if (user == null) throw new ArgumentNullException("user"); if (media == null) throw new ArgumentNullException("media"); - return HasPathAccess(media.Path, user.StartMediaId, Constants.System.RecycleBinMedia); + return HasPathAccess(media.Path, user.StartMediaIds, Constants.System.RecycleBinMedia); } /// diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 81a4b88289..e7f2aba08b 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; @@ -31,8 +32,8 @@ namespace Umbraco.Core.Persistence.Factories user.DisableChangeTracking(); user.Key = guidId; - user.StartContentId = dto.ContentStartId; - user.StartMediaId = dto.MediaStartId ?? -1; + user.StartContentIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.Id).ToArray(); + user.StartMediaIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int) UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.Id).ToArray(); user.IsLockedOut = dto.NoConsole; user.IsApproved = dto.Disabled == false; user.Language = dto.UserLanguage; @@ -59,9 +60,7 @@ namespace Umbraco.Core.Persistence.Factories public static UserDto BuildDto(IUser entity) { var dto = new UserDto - { - ContentStartId = entity.StartContentId, - MediaStartId = entity.StartMediaId, + { Disabled = entity.IsApproved == false, Email = entity.Email, Login = entity.Username, @@ -78,6 +77,26 @@ namespace Umbraco.Core.Persistence.Factories UpdateDate = entity.UpdateDate }; + foreach (var startNodeId in entity.StartContentIds) + { + dto.UserStartNodeDtos.Add(new UserStartNodeDto + { + StartNode = startNodeId, + StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Content, + UserId = entity.Id + }); + } + + foreach (var startNodeId in entity.StartMediaIds) + { + dto.UserStartNodeDtos.Add(new UserStartNodeDto + { + StartNode = startNodeId, + StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Media, + UserId = entity.Id + }); + } + if (entity.HasIdentity) { dto.Id = entity.Id.SafeCast(); diff --git a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs index 9ea69abbf6..06e906143a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs @@ -34,9 +34,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.RawPasswordValue, dto => dto.Password); CacheMap(src => src.Name, dto => dto.UserName); //NOTE: This column in the db is *not* used! - //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); - CacheMap(src => src.StartMediaId, dto => dto.MediaStartId); - CacheMap(src => src.StartContentId, dto => dto.ContentStartId); + //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); CacheMap(src => src.IsApproved, dto => dto.Disabled); CacheMap(src => src.IsLockedOut, dto => dto.NoConsole); CacheMap(src => src.Language, dto => dto.UserLanguage); diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 2e0bbf9ad2..456859a5ea 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -169,8 +169,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateUmbracoUserData() { - _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, ContentStartId = -1, MediaStartId = -1, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); - //_database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); + _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); } private void CreateUmbracoUserGroupData() diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 61a6e66bb4..d23b23b4b6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -90,6 +90,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {51, typeof (User2UserGroupDto) }, {52, typeof (UserGroup2NodePermissionDto) }, {53, typeof (UserGroup2AppDto) }, + {54, typeof (UserStartNodeDto) }, }; #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 45438da9d8..c11c413f56 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -143,7 +143,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial } //if the error is for umbracoUserGroup it must be the previous version to 7.7 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoUserGroup")))) + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoUserStartNode")))) { return new Version(7, 6, 0); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs index 5e99a47417..af9805221d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs @@ -3,6 +3,7 @@ using System.Data; using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.SqlSyntax; @@ -63,8 +64,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe Create.Table(); updated = true; } + + + return updated; - } + } private void MigrateUserTypesToGroups() { @@ -158,7 +162,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe if (tables.InvariantContains("umbracoUserType") && tables.InvariantContains("umbracoUser")) { if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id"))) - { + { Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser"); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs new file mode 100644 index 0000000000..086949663b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs @@ -0,0 +1,49 @@ +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero +{ + [Migration("7.7.0", 2, Constants.System.UmbracoMigrationName)] + public class AddUserStartNodeTable : MigrationBase + { + public AddUserStartNodeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) + { + } + + public override void Up() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains("umbracoUserStartNode") == false) + { + Create.Table(); + + MigrateUserStartNodes(); + + //now remove the old columns + + Delete.Column("startStructureID").FromTable("umbracoUser"); + Delete.Column("startMediaID").FromTable("umbracoUser"); + } + } + + private void MigrateUserStartNodes() + { + Execute.Sql(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) + SELECT id, startStructureID, 1 + FROM umbracoUser + WHERE startStructureID IS NOT NULL AND startStructureID > 0 AND startStructureID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Document + "')"); + + Execute.Sql(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) + SELECT id, startMediaID, 2 + FROM umbracoUser + WHERE startMediaID IS NOT NULL AND startMediaID > 0 AND startMediaID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Media + "')"); + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs index 0d9b1d5552..435b02c2e3 100644 --- a/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs +++ b/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Persistence.Relators { private UserDto _currentUser; - internal UserDto Map(UserDto user, UserGroupDto group, UserGroup2AppDto section) + internal UserDto Map(UserDto user, UserGroupDto group, UserGroup2AppDto section, UserStartNodeDto startNode) { // Terminating call. Since we can return null from this function // we need to be ready for PetaPoco to callback later with null @@ -20,6 +20,7 @@ namespace Umbraco.Core.Persistence.Relators if (_currentUser != null && _currentUser.Id == user.Id) { AddOrUpdateGroup(group, section); + AddOrUpdateStartNode(startNode); // Return null to indicate we're not done with this object yet return null; @@ -34,13 +35,33 @@ namespace Umbraco.Core.Persistence.Relators // Setup the new current user _currentUser = user; _currentUser.UserGroupDtos = new List(); + _currentUser.UserStartNodeDtos = new List(); AddOrUpdateGroup(group, section); + AddOrUpdateStartNode(startNode); // Return the now populated previous user (or null if first time through) return prev; } + private void AddOrUpdateStartNode(UserStartNodeDto startNode) + { + //this can be null since we are left joining + if (startNode == null || startNode.Id == default(int)) + return; + + //check if this is a new start node + var latestStartNode = _currentUser.UserStartNodeDtos.Count > 0 + ? _currentUser.UserStartNodeDtos[_currentUser.UserStartNodeDtos.Count - 1] + : null; + + if (latestStartNode == null || latestStartNode.Id != startNode.Id) + { + //add the current (new) start node + _currentUser.UserStartNodeDtos.Add(startNode); + } + } + private void AddOrUpdateGroup(UserGroupDto group, UserGroup2AppDto section) { //I don't even think this situation can happen but if it could, we'd want the section added to the latest group check if this is a new group @@ -50,21 +71,21 @@ namespace Umbraco.Core.Persistence.Relators } //this can be null since we are doing a left join - if (group != null && group.Alias.IsNullOrWhiteSpace() == false) - { - //check if this is a new group - var latestGroup = _currentUser.UserGroupDtos.Count > 0 - ? _currentUser.UserGroupDtos[_currentUser.UserGroupDtos.Count - 1] - : null; + if (group == null || group.Alias.IsNullOrWhiteSpace()) + return; - if (latestGroup == null || latestGroup.Id != group.Id) - { - //add the current (new) group - _currentUser.UserGroupDtos.Add(group); - } - - AddSection(section); + //check if this is a new group + var latestGroup = _currentUser.UserGroupDtos.Count > 0 + ? _currentUser.UserGroupDtos[_currentUser.UserGroupDtos.Count - 1] + : null; + + if (latestGroup == null || latestGroup.Id != group.Id) + { + //add the current (new) group + _currentUser.UserGroupDtos.Add(group); } + + AddSection(section); } private void AddSection(UserGroup2AppDto section) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index 59335daac0..63a6900634 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -54,5 +54,8 @@ namespace Umbraco.Core.Persistence.Repositories /// Optional parameter to filter by specfied user state /// IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Expression> orderBy, Direction orderDirection, string[] userGroups = null, UserState? userState = null, IQuery filter = null); + + IProfile GetProfile(string username); + IProfile GetProfile(int id); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 4c203f6dbb..4b05b5a47a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -38,18 +38,45 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where(GetBaseWhereClause(), new { Id = id }); sql //must be included for relator to work .OrderBy(d => d.Id, SqlSyntax) - .OrderBy(d => d.Id, SqlSyntax); + .OrderBy(d => d.Id, SqlSyntax) + .OrderBy(d => d.Id, SqlSyntax); - var dto = Database.Fetch(new UserGroupRelator().Map, sql) + var dto = Database.Fetch(new UserGroupRelator().Map, sql) .FirstOrDefault(); if (dto == null) - return null; + return null; var user = UserFactory.BuildEntity(dto); return user; } - + + public IProfile GetProfile(string username) + { + var sql = GetBaseQuery(false).Where(userDto => userDto.UserName == username, SqlSyntax); + + var dto = Database.Fetch(sql) + .FirstOrDefault(); + + if (dto == null) + return null; + + return new UserProfile(dto.Id, dto.UserName); + } + + public IProfile GetProfile(int id) + { + var sql = GetBaseQuery(false).Where(userDto => userDto.Id == id, SqlSyntax); + + var dto = Database.Fetch(sql) + .FirstOrDefault(); + + if (dto == null) + return null; + + return new UserProfile(dto.Id, dto.UserName); + } + protected override IEnumerable PerformGetAll(params int[] ids) { var sql = GetQueryWithGroups(); @@ -59,9 +86,10 @@ namespace Umbraco.Core.Persistence.Repositories } sql //must be included for relator to work .OrderBy(d => d.Id, SqlSyntax) - .OrderBy(d => d.Id, SqlSyntax); + .OrderBy(d => d.Id, SqlSyntax) + .OrderBy(d => d.Id, SqlSyntax); - var users = ConvertFromDtos(Database.Fetch(new UserGroupRelator().Map, sql)) + var users = ConvertFromDtos(Database.Fetch(new UserGroupRelator().Map, sql)) .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. return users; @@ -74,9 +102,10 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate(); sql //must be included for relator to work .OrderBy(d => d.Id, SqlSyntax) - .OrderBy(d => d.Id, SqlSyntax); + .OrderBy(d => d.Id, SqlSyntax) + .OrderBy(d => d.Id, SqlSyntax); - var dtos = Database.Fetch(new UserGroupRelator().Map, sql) + var dtos = Database.Fetch(new UserGroupRelator().Map, sql) .DistinctBy(x => x.Id); var users = ConvertFromDtos(dtos) @@ -110,7 +139,7 @@ namespace Umbraco.Core.Persistence.Repositories private Sql GetQueryWithGroups() { //base query includes user groups - var sql = GetBaseQuery("umbracoUser.*, umbracoUserGroup.*, umbracoUserGroup2App.*"); + var sql = GetBaseQuery("umbracoUser.*, umbracoUserGroup.*, umbracoUserGroup2App.*, umbracoUserStartNode.*"); AddGroupLeftJoin(sql); return sql; } @@ -122,7 +151,9 @@ namespace Umbraco.Core.Persistence.Repositories .LeftJoin(SqlSyntax) .On(SqlSyntax, dto => dto.Id, dto => dto.UserGroupId) .LeftJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.UserGroupId, dto => dto.Id); + .On(SqlSyntax, dto => dto.UserGroupId, dto => dto.Id) + .LeftJoin(SqlSyntax) + .On(SqlSyntax, dto => dto.UserId, dto => dto.Id); } private Sql GetBaseQuery(string columns) @@ -396,6 +427,8 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, mappedField, orderDirection, userGroups, userState, filter); } + + private IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, string[] userGroups = null, UserState? userState = null, IQuery filter = null) { if (string.IsNullOrWhiteSpace(orderBy)) throw new ArgumentException("Value cannot be null or whitespace.", "orderBy"); @@ -448,7 +481,7 @@ namespace Umbraco.Core.Persistence.Repositories string sqlStringCount, sqlStringPage; Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); - var sqlQueryFull = GetBaseQuery("umbracoUser.*, umbracoUserGroup.*, umbracoUserGroup2App.*"); + var sqlQueryFull = GetBaseQuery("umbracoUser.*, umbracoUserGroup.*, umbracoUserGroup2App.*, umbracoUserStartNode.*"); var fullQueryWithPagedInnerJoin = sqlQueryFull .Append("INNER JOIN (") @@ -464,7 +497,7 @@ namespace Umbraco.Core.Persistence.Repositories GetFilteredSqlForPagedResults(fullQueryWithPagedInnerJoin, filterSql), orderDirection, orderBy); - var users = ConvertFromDtos(Database.Fetch(new UserGroupRelator().Map, fullQuery)) + var users = ConvertFromDtos(Database.Fetch(new UserGroupRelator().Map, fullQuery)) .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. return users; diff --git a/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs b/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs index 7c2373a000..63880e369e 100644 --- a/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs +++ b/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs @@ -35,8 +35,8 @@ namespace Umbraco.Core.Security AllowedApplications = user.AllowedSections, Culture = user.Culture, Roles = user.Roles.Select(x => x.RoleId).ToArray(), - StartContentNode = user.StartContentId, - StartMediaNode = user.StartMediaId, + StartContentNodes = user.StartContentIds, + StartMediaNodes = user.StartMediaIds, SessionId = user.SecurityStamp }); diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 4206cc731e..611242b088 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -75,8 +75,8 @@ namespace Umbraco.Core.Security Language = user.Culture ?? Configuration.GlobalSettings.DefaultUILanguage, Name = user.Name, Username = user.UserName, - StartContentId = user.StartContentId == 0 ? -1 : user.StartContentId, - StartMediaId = user.StartMediaId == 0 ? -1 : user.StartMediaId, + StartContentIds = user.StartContentIds ?? new int[] { }, + StartMediaIds = user.StartMediaIds ?? new int[] { }, IsLockedOut = user.IsLockedOut, IsApproved = true }; @@ -673,15 +673,15 @@ namespace Umbraco.Core.Security anythingChanged = true; user.Language = identityUser.Culture; } - if (user.StartMediaId != identityUser.StartMediaId) + if (user.StartMediaIds.UnsortedSequenceEqual(identityUser.StartMediaIds) == false) { anythingChanged = true; - user.StartMediaId = identityUser.StartMediaId; + user.StartMediaIds = identityUser.StartMediaIds; } - if (user.StartContentId != identityUser.StartContentId) + if (user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds)) { anythingChanged = true; - user.StartContentId = identityUser.StartContentId; + user.StartContentIds = identityUser.StartContentIds; } if (user.SecurityStamp != identityUser.SecurityStamp) { diff --git a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs index 1bc9902da5..39931341fa 100644 --- a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs @@ -48,12 +48,20 @@ namespace Umbraco.Core.Security || realName == null || session == null) throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since there are missing required claims"); - int startContentIdAsInt; - int startMediaIdAsInt; - if (int.TryParse(startContentId, out startContentIdAsInt) == false || int.TryParse(startMediaId, out startMediaIdAsInt) == false) + int[] startContentIdsAsInt; + int[] startMediaIdsAsInt; + if (startContentId.DetectIsJson() == false || startMediaId.DetectIsJson() == false) + throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the data is not formatted correctly - either content or media start Ids are not JSON"); + + try { - throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the data is not formatted correctly"); + startContentIdsAsInt = JsonConvert.DeserializeObject(startContentId); + startMediaIdsAsInt = JsonConvert.DeserializeObject(startMediaId); } + catch (Exception e) + { + throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the data is not formatted correctly - either content or media start Ids could not be parsed as JSON", e); + } var roles = identity.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToList(); var allowedApps = identity.FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToList(); @@ -67,8 +75,8 @@ namespace Umbraco.Core.Security Roles = roles.ToArray(), Username = username, RealName = realName, - StartContentNode = startContentIdAsInt, - StartMediaNode = startMediaIdAsInt + StartContentNodes = startContentIdsAsInt, + StartMediaNodes = startMediaIdsAsInt }; return new UmbracoBackOfficeIdentity(identity, userData); @@ -202,10 +210,10 @@ namespace Umbraco.Core.Security AddClaim(new Claim(ClaimTypes.GivenName, UserData.RealName, ClaimValueTypes.String, Issuer, Issuer, this)); if (HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false) - AddClaim(new Claim(Constants.Security.StartContentNodeIdClaimType, StartContentNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); + AddClaim(new Claim(Constants.Security.StartContentNodeIdClaimType, JsonConvert.SerializeObject(StartContentNodes), ClaimValueTypes.Integer32, Issuer, Issuer, this)); if (HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false) - AddClaim(new Claim(Constants.Security.StartMediaNodeIdClaimType, StartMediaNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); + AddClaim(new Claim(Constants.Security.StartMediaNodeIdClaimType, JsonConvert.SerializeObject(StartMediaNodes), ClaimValueTypes.Integer32, Issuer, Issuer, this)); if (HasClaim(x => x.Type == ClaimTypes.Locality) == false) AddClaim(new Claim(ClaimTypes.Locality, Culture, ClaimValueTypes.String, Issuer, Issuer, this)); @@ -259,14 +267,14 @@ namespace Umbraco.Core.Security get { return _currentIssuer; } } - public int StartContentNode + public int[] StartContentNodes { - get { return UserData.StartContentNode; } + get { return UserData.StartContentNodes; } } - public int StartMediaNode + public int[] StartMediaNodes { - get { return UserData.StartMediaNode; } + get { return UserData.StartMediaNodes; } } public string[] AllowedApplications diff --git a/src/Umbraco.Core/Security/UserData.cs b/src/Umbraco.Core/Security/UserData.cs index 407d2782dd..31a7d50f25 100644 --- a/src/Umbraco.Core/Security/UserData.cs +++ b/src/Umbraco.Core/Security/UserData.cs @@ -48,10 +48,10 @@ namespace Umbraco.Core.Security public string RealName { get; set; } [DataMember(Name = "startContent")] - public int StartContentNode { get; set; } + public int[] StartContentNodes { get; set; } [DataMember(Name = "startMedia")] - public int StartMediaNode { get; set; } + public int[] StartMediaNodes { get; set; } [DataMember(Name = "allowedApps")] public string[] AllowedApplications { get; set; } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 8072ecb94d..a801986b60 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -107,9 +107,7 @@ namespace Umbraco.Core.Services Language = GlobalSettings.DefaultUILanguage, Name = username, RawPasswordValue = passwordValue, - Username = username, - StartContentId = -1, - StartMediaId = -1, + Username = username, IsLockedOut = false, IsApproved = true }; @@ -604,8 +602,11 @@ namespace Umbraco.Core.Services /// public IProfile GetProfileById(int id) { - var user = GetUserById(id); - return user.ProfileData; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateUserRepository(uow); + return repository.GetProfile(id); + } } /// @@ -615,8 +616,11 @@ namespace Umbraco.Core.Services /// public IProfile GetProfileByUserName(string username) { - var user = GetByUsername(username); - return user.ProfileData; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateUserRepository(uow); + return repository.GetProfile(username); + } } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b6728affdf..52a367224b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -343,8 +343,10 @@ + + @@ -510,6 +512,7 @@ + diff --git a/src/Umbraco.Tests/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Controllers/UsersControllerTests.cs index b722bc789b..7af3d5e340 100644 --- a/src/Umbraco.Tests/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Controllers/UsersControllerTests.cs @@ -36,6 +36,52 @@ namespace Umbraco.Tests.Controllers public class UsersControllerTests : BaseDatabaseFactoryTest { + [Test] + public async void Save_User() + { + var runner = new TestRunner((message, helper) => + { + //setup some mocks + Umbraco.Core.Configuration.GlobalSettings.HasSmtpServer = true; + + var userServiceMock = Mock.Get(helper.UmbracoContext.Application.Services.UserService); + + userServiceMock.Setup(service => service.Save(It.IsAny(), It.IsAny())) + .Callback((IUser u, bool raiseEvents) => + { + u.Id = 1234; + }); + userServiceMock.Setup(service => service.GetAllUserGroups(It.IsAny())) + .Returns(Enumerable.Empty); + + //we need to manually apply automapper mappings with the mocked applicationcontext + InitializeMappers(helper.UmbracoContext.Application); + + return new UsersController(helper.UmbracoContext); + }); + + var invite = new UserInvite + { + Id = -1, + Email = "test@test.com", + Message = "Hello test!", + Name = "Test", + UserGroups = new[] { "writers" } + }; + var response = await runner.Execute("Users", "PostSaveUser", HttpMethod.Post, + new ObjectContent(invite, new JsonMediaTypeFormatter())); + + var obj = JsonConvert.DeserializeObject(response.Item2); + + Assert.AreEqual(invite.Name, obj.Name); + Assert.AreEqual(1234, obj.Id); + Assert.AreEqual(invite.Email, obj.Email); + foreach (var group in invite.UserGroups) + { + Assert.IsTrue(obj.UserGroups.Contains(group)); + } + } + [Test] public async void Invite_User() { diff --git a/src/Umbraco.Tests/Models/UserExtensionsTests.cs b/src/Umbraco.Tests/Models/UserExtensionsTests.cs index 84537987c9..c6f9c55ead 100644 --- a/src/Umbraco.Tests/Models/UserExtensionsTests.cs +++ b/src/Umbraco.Tests/Models/UserExtensionsTests.cs @@ -17,7 +17,7 @@ namespace Umbraco.Tests.Models public void Determines_Path_Based_Access_To_Content(int userId, string contentPath, bool outcome) { var userMock = new Mock(); - userMock.Setup(u => u.StartContentId).Returns(userId); + userMock.Setup(u => u.StartContentIds).Returns(new[]{ userId }); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns(contentPath); diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index 5bdc842cc1..104f435bf5 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -35,8 +35,8 @@ namespace Umbraco.Tests.Models PasswordQuestion = "question", //ProviderUserKey = "user key", SessionTimeout = 5, - StartContentId = 3, - StartMediaId = 8, + StartContentIds = new []{ 3 }, + StartMediaIds = new[]{ 8 }, Username = "username" }; @@ -82,8 +82,8 @@ namespace Umbraco.Tests.Models PasswordQuestion = "question", //ProviderUserKey = "user key", SessionTimeout = 5, - StartContentId = 3, - StartMediaId = 8, + StartContentIds = new[]{ 3 }, + StartMediaIds = new []{ 8 }, Username = "username" }; diff --git a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs index e9d9163b12..9f336d404e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repo = new NotificationsRepository(unitOfWork)) { - var userDto = new UserDto {ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; + var userDto = new UserDto {Email = "test", Login = "test", Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 10; i++) { - var userDto = new UserDto {ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; + var userDto = new UserDto {Email = "test" + i, Login = "test" + i, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); @@ -109,7 +109,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 10; i++) { - var userDto = new UserDto {ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; + var userDto = new UserDto {Email = "test" + i, Login = "test" + i, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); @@ -128,7 +128,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repo = new NotificationsRepository(unitOfWork)) { - var userDto = new UserDto {ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; + var userDto = new UserDto {Email = "test", Login = "test", Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 0ad8fe1708..a4cf866cd2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -153,8 +153,8 @@ namespace Umbraco.Tests.Persistence.Repositories resolved.IsApproved = false; resolved.RawPasswordValue = "new"; resolved.IsLockedOut = true; - resolved.StartContentId = 10; - resolved.StartMediaId = 11; + resolved.StartContentIds = new []{ 10 }; + resolved.StartMediaIds = new []{ 11 }; resolved.Email = "new@new.com"; resolved.Username = "newName"; @@ -169,8 +169,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(updatedItem.IsApproved, Is.EqualTo(resolved.IsApproved)); Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(resolved.RawPasswordValue)); Assert.That(updatedItem.IsLockedOut, Is.EqualTo(resolved.IsLockedOut)); - Assert.That(updatedItem.StartContentId, Is.EqualTo(resolved.StartContentId)); - Assert.That(updatedItem.StartMediaId, Is.EqualTo(resolved.StartMediaId)); + Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(resolved.StartContentIds)); + Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(resolved.StartMediaIds)); Assert.That(updatedItem.Email, Is.EqualTo(resolved.Email)); Assert.That(updatedItem.Username, Is.EqualTo(resolved.Username)); Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); @@ -360,8 +360,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved)); Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue)); Assert.That(updatedItem.IsLockedOut, Is.EqualTo(originalUser.IsLockedOut)); - Assert.That(updatedItem.StartContentId, Is.EqualTo(originalUser.StartContentId)); - Assert.That(updatedItem.StartMediaId, Is.EqualTo(originalUser.StartMediaId)); + Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(originalUser.StartContentIds)); + Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(originalUser.StartMediaIds)); Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email)); Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username)); Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs b/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs index 813653fb7c..226b26990d 100644 --- a/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs +++ b/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs @@ -44,8 +44,8 @@ namespace Umbraco.Tests.Security Assert.AreEqual(sessionId, backofficeIdentity.SessionId); Assert.AreEqual("testing", backofficeIdentity.Username); Assert.AreEqual("hello world", backofficeIdentity.RealName); - Assert.AreEqual(-1, backofficeIdentity.StartContentNode); - Assert.AreEqual(5543, backofficeIdentity.StartMediaNode); + Assert.AreEqual(0, backofficeIdentity.StartContentNodes.Length); + Assert.IsTrue(backofficeIdentity.StartMediaNodes.UnsortedSequenceEqual(new []{ 5543 })); Assert.IsTrue(new[] {"content", "media"}.SequenceEqual(backofficeIdentity.AllowedApplications)); Assert.AreEqual("en-us", backofficeIdentity.Culture); Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles)); @@ -98,8 +98,7 @@ namespace Umbraco.Tests.Security Id = 1234, RealName = "hello world", Roles = new[] {"admin"}, - StartContentNode = -1, - StartMediaNode = 654, + StartMediaNodes = new []{ 654 }, Username = "testing" }; @@ -119,8 +118,7 @@ namespace Umbraco.Tests.Security Id = 1234, RealName = "hello world", Roles = new[] { "admin" }, - StartContentNode = -1, - StartMediaNode = 654, + StartMediaNodes =new []{ 654 } , Username = "testing" }; @@ -146,8 +144,7 @@ namespace Umbraco.Tests.Security Id = 1234, RealName = "hello world", Roles = new[] { "admin" }, - StartContentNode = -1, - StartMediaNode = 654, + StartMediaNodes = new []{ 654 }, Username = "testing" }; @@ -170,8 +167,7 @@ namespace Umbraco.Tests.Security Id = 1234, RealName = "hello world", Roles = new[] { "admin" }, - StartContentNode = -1, - StartMediaNode = 654, + StartMediaNodes = new []{ 654 }, Username = "testing" }; diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index d99f6e70d2..5a0ad30169 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using umbraco.BusinessLogic.Actions; +using Umbraco.Core; using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Services @@ -684,8 +685,8 @@ namespace Umbraco.Tests.Services Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved)); Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue)); Assert.That(updatedItem.IsLockedOut, Is.EqualTo(originalUser.IsLockedOut)); - Assert.That(updatedItem.StartContentId, Is.EqualTo(originalUser.StartContentId)); - Assert.That(updatedItem.StartMediaId, Is.EqualTo(originalUser.StartMediaId)); + Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(originalUser.StartContentIds)); + Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(originalUser.StartMediaIds)); Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email)); Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username)); Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs index 2dbfa757f2..0cfcb47ade 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs @@ -31,9 +31,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting Roles = new[] { "admin" }, AllowedApplications = new[] { "content", "media", "members" }, Culture = "en-US", - RealName = "Admin", - StartContentNode = -1, - StartMediaNode = -1, + RealName = "Admin", Username = "admin" }); diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 7299d9758e..127269a384 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -90,8 +90,8 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting && u.Id == (int) backofficeIdentity.Id && u.Language == "en" && u.Name == backofficeIdentity.RealName - && u.StartContentId == backofficeIdentity.StartContentNode - && u.StartMediaId == backofficeIdentity.StartMediaNode + && u.StartContentIds == backofficeIdentity.StartContentNodes + && u.StartMediaIds == backofficeIdentity.StartMediaNodes && u.Username == backofficeIdentity.Username)); //mock Validate diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs index 53b53e634f..ce2931e99e 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs @@ -15,8 +15,6 @@ namespace Umbraco.Tests.TestHelpers.Entities Name = "TestUser" + suffix, RawPasswordValue = "testing", IsLockedOut = false, - StartContentId = -1, - StartMediaId = -1, Email = "test" + suffix + "@test.com", Username = "TestUser" + suffix }; diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index c7922b9d58..2b832c098c 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -96,7 +96,7 @@ namespace Umbraco.Tests.UmbracoExamine } if (userService == null) { - userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); + userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == 0 && p.Name == "admin")); } if (mediaService == null) { diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs index 9634bb164a..7782612420 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -63,7 +63,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(9876); + userMock.Setup(u => u.StartContentIds).Returns(new[]{ 9876 }); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -89,7 +89,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -118,7 +118,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -147,7 +147,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; //act @@ -163,7 +163,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; //act @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(1234); + userMock.Setup(u => u.StartContentIds).Returns(new []{ 1234 }); var user = userMock.Object; //act @@ -195,7 +195,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(1234); + userMock.Setup(u => u.StartContentIds).Returns(new []{ 1234 }); var user = userMock.Object; //act @@ -211,7 +211,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); @@ -235,7 +235,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); @@ -259,7 +259,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); @@ -283,7 +283,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs index c776924af7..bc4046b39e 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(5); + userMock.Setup(u => u.StartContentIds).Returns(new []{ 5 }); var user = userMock.Object; att.FilterBasedOnStartNode(list, user); @@ -105,7 +105,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs index 2d5420409c..e65b1a1b19 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartMediaId).Returns(-1); + userMock.Setup(u => u.StartMediaIds).Returns(new int[0]); var user = userMock.Object; var mediaMock = new Mock(); mediaMock.Setup(m => m.Path).Returns("-1,1234,5678"); @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartMediaId).Returns(-1); + userMock.Setup(u => u.StartMediaIds).Returns(new int[0]); var user = userMock.Object; var mediaMock = new Mock(); mediaMock.Setup(m => m.Path).Returns("-1,1234,5678"); @@ -59,7 +59,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartMediaId).Returns(9876); + userMock.Setup(u => u.StartMediaIds).Returns(new[]{ 9876 }); var user = userMock.Object; var mediaMock = new Mock(); mediaMock.Setup(m => m.Path).Returns("-1,1234,5678"); @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartMediaId).Returns(-1); + userMock.Setup(u => u.StartMediaIds).Returns(new int[]{}); var user = userMock.Object; //act @@ -97,7 +97,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartMediaId).Returns(1234); + userMock.Setup(u => u.StartMediaIds).Returns(new[]{ 1234 }); var user = userMock.Object; //act @@ -113,7 +113,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartMediaId).Returns(-1); + userMock.Setup(u => u.StartMediaIds).Returns(new int[]{}); var user = userMock.Object; //act @@ -129,7 +129,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartMediaId).Returns(1234); + userMock.Setup(u => u.StartMediaIds).Returns(new[]{ 1234 }); var user = userMock.Object; //act diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index c95e1a7b7f..1a652a787c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -26,7 +26,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat enablelistviewexpand: '@' }, - compile: function(element, attrs) { + compile: function (element, attrs) { //config //var showheader = (attrs.showheader !== 'false'); var hideoptions = (attrs.hideoptions === 'true') ? "hide-options" : ""; @@ -44,7 +44,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat element.replaceWith(template); - return function(scope, elem, attr, controller) { + return function (scope, elem, attr, controller) { //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover @@ -84,16 +84,16 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat function setupExternalEvents() { if (scope.eventhandler) { - scope.eventhandler.clearCache = function(section) { + scope.eventhandler.clearCache = function (section) { treeService.clearCache({ section: section }); }; - scope.eventhandler.load = function(section) { + scope.eventhandler.load = function (section) { scope.section = section; loadTree(); }; - scope.eventhandler.reloadNode = function(node) { + scope.eventhandler.reloadNode = function (node) { if (!node) { node = scope.currentNode; @@ -108,7 +108,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat Used to do the tree syncing. If the args.tree is not specified we are assuming it has been specified previously using the _setActiveTreeType */ - scope.eventhandler.syncTree = function(args) { + scope.eventhandler.syncTree = function (args) { if (!args) { throw "args cannot be null"; } @@ -146,9 +146,16 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat // and previous so that the tree syncs properly. The tree syncs from the top down and if there are parts // of the tree's path in there that don't actually exist in the dom/model then syncing will not work. - userService.getCurrentUser().then(function(userData) { + userService.getCurrentUser().then(function (userData) { + + var startNodes = []; + for (var i = 0; i < userData.startContentIds; i++) { + startNodes.push(userData.startContentIds[i]); + } + for (var j = 0; j < userData.startMediaIds; j++) { + startNodes.push(userData.startMediaIds[j]); + } - var startNodes = [userData.startContentId, userData.startMediaId]; _.each(startNodes, function (i) { var found = _.find(args.path, function (p) { return String(p) === String(i); @@ -175,7 +182,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat node's children - this is synonymous with the legacy refreshTree method - again should not be used and should only be used for the legacy code to work. */ - scope.eventhandler._setActiveTreeType = function(treeAlias, loadChildren) { + scope.eventhandler._setActiveTreeType = function (treeAlias, loadChildren) { loadActiveTree(treeAlias, loadChildren); }; } @@ -210,7 +217,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat function doLoad(tree) { var childrenAndSelf = [tree].concat(tree.children); scope.activeTree = _.find(childrenAndSelf, function (node) { - if(node && node.metaData && node.metaData.treeAlias) { + if (node && node.metaData && node.metaData.treeAlias) { return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); } return false; @@ -223,7 +230,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //This is only used for the legacy tree method refreshTree! if (loadChildren) { scope.activeTree.expanded = true; - scope.loadChildren(scope.activeTree, false).then(function() { + scope.loadChildren(scope.activeTree, false).then(function () { emitEvent("activeTreeLoaded", { tree: scope.activeTree }); }); } @@ -236,7 +243,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat doLoad(scope.tree.root); } else { - scope.eventhandler.one("treeLoaded", function(e, args) { + scope.eventhandler.one("treeLoaded", function (e, args) { doLoad(args.tree.root); }); } @@ -261,7 +268,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat } treeService.getTree(args) - .then(function(data) { + .then(function (data) { //set the data once we have it scope.tree = data; @@ -274,7 +281,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat emitEvent("treeLoaded", { tree: scope.tree }); emitEvent("treeNodeExpanded", { tree: scope.tree, node: scope.tree.root, children: scope.tree.root.children }); - }, function(reason) { + }, function (reason) { scope.loading = false; notificationsService.error("Tree Error", reason); }); @@ -306,8 +313,8 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat scope.selectEnabledNodeClass = function (node) { return node ? node.selected ? - 'icon umb-tree-icon sprTree icon-check green temporary' : - '' : + 'icon umb-tree-icon sprTree icon-check green temporary' : + '' : ''; }; @@ -315,7 +322,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat * This changes dynamically based on if we are changing sections or just loading normal tree data. * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations. */ - scope.animation = function() { + scope.animation = function () { if (deleteAnimations && scope.tree && scope.tree.root && scope.tree.root.expanded) { return { leave: 'tree-node-delete-leave' }; } @@ -325,7 +332,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat }; /* helper to force reloading children of a tree node */ - scope.loadChildren = function(node, forceReload) { + scope.loadChildren = function (node, forceReload) { var deferred = $q.defer(); //emit treeNodeExpanding event, if a callback object is set on the tree @@ -339,7 +346,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat if (forceReload || (node.hasChildren && node.children.length === 0)) { //get the children from the tree service treeService.loadNodeChildren({ node: node, section: scope.section }) - .then(function(data) { + .then(function (data) { //emit expanded event emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data }); @@ -365,7 +372,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat The tree doesnt know about this, so it raises an event to tell the parent controller about it. */ - scope.options = function(n, ev) { + scope.options = function (n, ev) { emitEvent("treeOptionsClick", { element: elem, node: n, event: ev }); }; @@ -384,12 +391,12 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat emitEvent("treeNodeSelect", { element: elem, node: n, event: ev }); }; - scope.altSelect = function(n, ev) { + scope.altSelect = function (n, ev) { emitEvent("treeNodeAltSelect", { element: elem, tree: scope.tree, node: n, event: ev }); }; //watch for section changes - scope.$watch("section", function(newVal, oldVal) { + scope.$watch("section", function (newVal, oldVal) { if (!scope.tree) { loadTree(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 262b06dd50..7b7ea7e3b4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -235,16 +235,6 @@ angular.module('umbraco.services') var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; - //TODO: This is a mega backwards compatibility hack... These variables SHOULD NOT exist in the server variables - // since they are not supposed to be dynamic but I accidentally added them there in 7.1.5 IIRC so some people might - // now be relying on this :( - if (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables) { - Umbraco.Sys.ServerVariables["security"] = { - startContentId: data.startContentId, - startMediaId: data.startMediaId - }; - } - if (args && args.broadcastEvent) { //broadcast a global event, will inform listening controllers to load in the user specific data eventsService.emit("app.authenticated", result); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index 5ee103a32b..e76db90f47 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -108,15 +108,15 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", $scope.switchToMediaPicker = function () { userService.getCurrentUser().then(function (userData) { - dialogService.mediaPicker({ - startNodeId: userData.startMediaId, - callback: function (media) { - $scope.target.id = media.id; - $scope.target.isMedia = true; - $scope.target.name = media.name; - $scope.target.url = mediaHelper.resolveFile(media); - } - }); + dialogService.mediaPicker({ + startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], + callback: function(media) { + $scope.target.id = media.id; + $scope.target.isMedia = true; + $scope.target.name = media.name; + $scope.target.url = mediaHelper.resolveFile(media); + } + }); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index fde3e4cab3..9c5a2b888c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -89,23 +89,23 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $scope.switchToMediaPicker = function () { userService.getCurrentUser().then(function (userData) { - $scope.mediaPickerOverlay = { - view: "mediapicker", - startNodeId: userData.startMediaId, - show: true, - submit: function(model) { - var media = model.selectedImages[0]; + $scope.mediaPickerOverlay = { + view: "mediapicker", + startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], + show: true, + submit: function(model) { + var media = model.selectedImages[0]; - $scope.model.target.id = media.id; - $scope.model.target.udi = media.udi; - $scope.model.target.isMedia = true; - $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); + $scope.model.target.id = media.id; + $scope.model.target.udi = media.udi; + $scope.model.target.isMedia = true; + $scope.model.target.name = media.name; + $scope.model.target.url = mediaHelper.resolveFile(media); - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index 9b7c929367..b0b19a2716 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -279,7 +279,7 @@ function MediaFolderBrowserDashboardController($rootScope, $scope, $location, co currentUser = user; // check if the user start node is the dashboard - if(currentUser.startMediaId === -1) { + if (currentUser.startMediaIds.length === 0 || currentUser.startMediaIds.indexOf(-1) >= 0) { //get the system media listview contentTypeResource.getPropertyTypeScaffold(-96) @@ -305,7 +305,7 @@ function MediaFolderBrowserDashboardController($rootScope, $scope, $location, co } else { // redirect to start node - $location.path("/media/media/edit/" + currentUser.startMediaId); + $location.path("/media/media/edit/" + (currentUser.startMediaIds.length === 0 ? -1 : currentUser.startMediaIds[0])); } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index bb580a906d..57f7d06f80 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -4,7 +4,7 @@ angular.module("umbraco") if (!$scope.model.config.startNodeId) { userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaId; + $scope.model.config.startNodeId = userData.startMediaIds.length === 0 ? -1 : userData.startMediaIds[0]; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index e406f87aed..6ec9e74fa4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -28,7 +28,7 @@ currentTarget: currentTarget, onlyImages: true, showDetails: true, - startNodeId: userData.startMediaId, + startNodeId: userData.startMediaIds.length === 0 ? -1 : userData.startMediaIds[0], view: "mediapicker", show: true, submit: function(model) { 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 6652c52cc0..d09c89c445 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 @@ -10,7 +10,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl if (!$scope.model.config.startNodeId) { userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaId; + $scope.model.config.startNodeId = userData.startMediaIds.length === 0 ? -1 : userData.startMediaIds[0]; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index bbb8024a53..45e8856132 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -250,7 +250,7 @@ angular.module("umbraco") onlyImages: true, showDetails: true, disableFolderSelect: true, - startNodeId: userData.startMediaId, + startNodeId: userData.startMediaIds.length === 0 ? -1 : userData.startMediaIds[0], view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index da5e0c3a3b..6682b22389 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -800,16 +800,16 @@ namespace Umbraco.Web.Editors } var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : (nodeId == Constants.System.RecycleBinContent) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinContent.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : user.HasPathAccess(contentItem); + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartContentIds, + Constants.System.RecycleBinContent) + : (nodeId == Constants.System.RecycleBinContent) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinContent.ToInvariantString(), + user.StartContentIds, + Constants.System.RecycleBinContent) + : user.HasPathAccess(contentItem); if (hasPathAccess == false) { diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 48da53e263..e16d6da67a 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -597,6 +597,36 @@ namespace Umbraco.Web.Editors return ExamineSearch(query, entityType, 200, 0, out total, searchFrom); } + private void AddExamineSearchFrom(string searchFrom, StringBuilder sb) + { + //if searchFrom is specified and it is greater than 0 + int mediaSearchFrom; + if (searchFrom != null && int.TryParse(searchFrom, out mediaSearchFrom) && mediaSearchFrom > 0) + { + sb.Append("+__Path: \\-1*\\,"); + sb.Append(mediaSearchFrom.ToString(CultureInfo.InvariantCulture)); + sb.Append("\\,* "); + } + } + + private void AddExamineUserStartNode(int[] startNodes, StringBuilder sb) + { + //make sure only what the user is configured to view is found + if (startNodes.Length > 0) + sb.Append("+("); + foreach (var startNode in startNodes) + { + if (startNode > 0) + { + sb.Append("__Path: \\-1*\\,"); + sb.Append(startNode.ToString(CultureInfo.InvariantCulture)); + sb.Append("\\,* "); + } + } + if (startNodes.Length > 0) + sb.Append(")"); + } + /// /// Searches for results based on the entity type /// @@ -636,34 +666,16 @@ namespace Umbraco.Web.Editors case UmbracoEntityTypes.Media: type = "media"; - var mediaSearchFrom = int.MinValue; - - if (Security.CurrentUser.StartMediaId > 0 || - //if searchFrom is specified and it is greater than 0 - (searchFrom != null && int.TryParse(searchFrom, out mediaSearchFrom) && mediaSearchFrom > 0)) - { - sb.Append("+__Path: \\-1*\\,"); - sb.Append(mediaSearchFrom > 0 - ? mediaSearchFrom.ToString(CultureInfo.InvariantCulture) - : Security.CurrentUser.StartMediaId.ToString(CultureInfo.InvariantCulture)); - sb.Append("\\,* "); - } + AddExamineSearchFrom(searchFrom, sb); + AddExamineUserStartNode(Security.CurrentUser.StartMediaIds, sb); + break; case UmbracoEntityTypes.Document: type = "content"; - var contentSearchFrom = int.MinValue; - - if (Security.CurrentUser.StartContentId > 0 || - //if searchFrom is specified and it is greater than 0 - (searchFrom != null && int.TryParse(searchFrom, out contentSearchFrom) && contentSearchFrom > 0)) - { - sb.Append("+__Path: \\-1*\\,"); - sb.Append(contentSearchFrom > 0 - ? contentSearchFrom.ToString(CultureInfo.InvariantCulture) - : Security.CurrentUser.StartContentId.ToString(CultureInfo.InvariantCulture)); - sb.Append("\\,* "); - } + AddExamineSearchFrom(searchFrom, sb); + AddExamineUserStartNode(Security.CurrentUser.StartContentIds, sb); + break; default: throw new NotSupportedException("The " + typeof(EntityController) + " currently does not support searching against object type " + entityType); diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 4980605374..4cac919b1b 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -884,16 +884,16 @@ namespace Umbraco.Web.Editors } var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : (nodeId == Constants.System.RecycleBinMedia) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinMedia.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : user.HasPathAccess(media); + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartMediaIds, + Constants.System.RecycleBinMedia) + : (nodeId == Constants.System.RecycleBinMedia) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinMedia.ToInvariantString(), + user.StartMediaIds, + Constants.System.RecycleBinMedia) + : user.HasPathAccess(media); return hasPathAccess; } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index c6ce2ecc81..73757f5427 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -98,6 +98,34 @@ namespace Umbraco.Web.Editors }; } + /// + /// Creates a new user + /// + /// + /// + public UserDisplay PostCreateUser(UserInvite userSave) + { + if (userSave == null) throw new ArgumentNullException("userSave"); + + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var existing = Services.UserService.GetByEmail(userSave.Email); + if (existing != null) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var user = Mapper.Map(userSave); + + Services.UserService.Save(user); + + return Mapper.Map(user); + } + /// /// Invites a user /// @@ -158,8 +186,31 @@ namespace Umbraco.Web.Editors if (found == null) throw new HttpResponseException(HttpStatusCode.NotFound); + var hasErrors = false; + + var existing = Services.UserService.GetByEmail(userSave.Email); + if (existing != null && existing.Id != (int)userSave.Id) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + hasErrors = true; + } + existing = Services.UserService.GetByUsername(userSave.Name); + if (existing != null && existing.Id != (int)userSave.Id) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + hasErrors = true; + } + + if (hasErrors) + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + //TODO: More validation, password changing logic, persisting - return Mapper.Map(found); + + var user = Mapper.Map(userSave); + + Services.UserService.Save(user); + + return Mapper.Map(user); } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs index 9b29e132af..3cdf1cc3b6 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs @@ -30,11 +30,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "remainingAuthSeconds")] public double SecondsUntilTimeout { get; set; } - [DataMember(Name = "startContentId")] - public int StartContentId { get; set; } + [DataMember(Name = "startContentIds")] + public int[] StartContentIds { get; set; } - [DataMember(Name = "startMediaId")] - public int StartMediaId { get; set; } + [DataMember(Name = "startMediaIds")] + public int[] StartMediaIds { get; set; } /// /// A list of sections the user is allowed to view. diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs index a0c2a8ef33..e4bbe7850b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs @@ -48,10 +48,9 @@ namespace Umbraco.Web.Models.ContentEditing /// /// Gets the available user groups (i.e. to populate a drop down) - /// The key is the Alias the value is the Name - the Alias is what is used in the UserGroup property and for persistence /// [DataMember(Name = "availableUserGroups")] - public IDictionary AvailableUserGroups { get; set; } + public IEnumerable AvailableUserGroups { get; set; } /// /// Gets the available cultures (i.e. to populate a drop down) @@ -60,26 +59,17 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "availableCultures")] public IDictionary AvailableCultures { get; set; } - //TODO: This will become StartContentIds as an array! - [DataMember(Name = "startContentId")] - public int StartContentId { get; set; } + [DataMember(Name = "startContentIds")] + public int[] StartContentIds { get; set; } - //TODO: This will become StartMediaIds as an array! - [DataMember(Name = "startMediaId")] - public int StartMediaId { get; set; } + [DataMember(Name = "startMediaIds")] + public int[] StartMediaIds { get; set; } - /// - /// A list of sections the user is allowed to view. - /// - [DataMember(Name = "allowedSections")] - public IEnumerable AllowedSections { get; set; } - - /// - /// Gets the available sections (i.e. to populate a drop down) - /// The key is the Alias the value is the Name - the Alias is what is used in the AllowedSections property and for persistence - /// - [DataMember(Name = "availableSections")] - public IDictionary AvailableSections { get; set; } + ///// + ///// A list of sections the user is allowed to view based on their current groups assigned + ///// + //[DataMember(Name = "allowedSections")] + //public IEnumerable AllowedSections { get; set; } /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs index 06e24b7d38..404b9925f9 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.Models.ContentEditing public UserGroupDisplay() { Notifications = new List(); - } + } /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. @@ -20,6 +20,13 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "sections")] public IEnumerable Sections { get; set; } + /// + /// Gets the available sections (i.e. to populate a drop down) + /// The key is the Alias the value is the Name - the Alias is what is used in the AllowedSections property and for persistence + /// + [DataMember(Name = "availableSections")] + public IEnumerable
    AvailableSections { get; set; } + [DataMember(Name = "startNodeContent")] public int StartContentId { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs index 8bef19d62f..8f42dbf169 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs @@ -30,11 +30,11 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public IEnumerable UserGroups { get; set; } - [DataMember(Name = "startContentId")] - public int StartContentId { get; set; } + [DataMember(Name = "startContentIds")] + public int[] StartContentIds { get; set; } - [DataMember(Name = "startMediaId")] - public int StartMediaId { get; set; } + [DataMember(Name = "startMediaIds")] + public int[] StartMediaIds { get; set; } [DataMember(Name = "allowedSections")] public IEnumerable AllowedSections { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index aa4ad4b6e4..96d52f53fd 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -16,8 +16,29 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - config.CreateMap() + config.CreateMap() .ConstructUsing(invite => new User(invite.Name, invite.Email, invite.Email, Guid.NewGuid().ToString("N"))) + .ForMember(user => user.Id, expression => expression.Ignore()) + .ForMember(user => user.SessionTimeout, expression => expression.Ignore()) + .ForMember(user => user.StartContentIds, expression => expression.Ignore()) + .ForMember(user => user.StartMediaIds, expression => expression.Ignore()) + .ForMember(user => user.Language, expression => expression.Ignore()) + .ForMember(user => user.SecurityStamp, expression => expression.Ignore()) + .ForMember(user => user.ProviderUserKey, expression => expression.Ignore()) + .ForMember(user => user.Username, expression => expression.Ignore()) + .ForMember(user => user.RawPasswordValue, expression => expression.Ignore()) + .ForMember(user => user.PasswordQuestion, expression => expression.Ignore()) + .ForMember(user => user.RawPasswordAnswerValue, expression => expression.Ignore()) + .ForMember(user => user.Comments, expression => expression.Ignore()) + .ForMember(user => user.IsApproved, expression => expression.Ignore()) + .ForMember(user => user.IsLockedOut, expression => expression.Ignore()) + .ForMember(user => user.LastLoginDate, expression => expression.Ignore()) + .ForMember(user => user.LastPasswordChangeDate, expression => expression.Ignore()) + .ForMember(user => user.LastLockoutDate, expression => expression.Ignore()) + .ForMember(user => user.FailedPasswordAttempts, expression => expression.Ignore()) + .ForMember(user => user.DeletedDate, expression => expression.Ignore()) + .ForMember(user => user.CreateDate, expression => expression.Ignore()) + .ForMember(user => user.UpdateDate, expression => expression.Ignore()) .AfterMap((invite, user) => { foreach (var group in invite.UserGroups) @@ -27,8 +48,9 @@ namespace Umbraco.Web.Models.Mapping }); config.CreateMap() - .ForMember(detail => detail.Notifications, opt => opt.Ignore()) + .ForMember(detail => detail.AvailableSections, opt => opt.MapFrom(x => applicationContext.Services.SectionService.GetSections())) .ForMember(detail => detail.Sections, opt => opt.MapFrom(x => x.AllowedSections)) + .ForMember(detail => detail.Notifications, opt => opt.Ignore()) .ForMember(detail => detail.Udi, opt => opt.Ignore()) .ForMember(detail => detail.Trashed, opt => opt.Ignore()) .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) @@ -37,15 +59,12 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups)) - .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember( detail => detail.AvailableUserGroups, - opt => opt.MapFrom(user => applicationContext.Services.UserService.GetAllUserGroups().ToDictionary(x => x.Alias, x => x.Name))) - .ForMember( - detail => detail.AvailableSections, - opt => opt.MapFrom(user => applicationContext.Services.SectionService.GetSections().ToDictionary(x => x.Alias, x => x.Name))) + opt => opt.MapFrom(user => applicationContext.Services.UserService.GetAllUserGroups())) .ForMember( detail => detail.AvailableCultures, opt => opt.MapFrom(user => applicationContext.Services.TextService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName))) @@ -66,8 +85,8 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) - .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember( detail => detail.EmailHash, @@ -76,8 +95,8 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => user.Id)) - .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture)) .ForMember(detail => detail.AllowedSections, opt => opt.MapFrom(user => user.AllowedSections)) .ForMember( @@ -94,8 +113,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections)) .ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name)) .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Groups.ToArray())) - .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaNodes, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp)); diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 05407a93e5..d610ed6945 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -243,8 +243,6 @@ namespace Umbraco.Web.Security Name = membershipUser.UserName, RawPasswordValue = Guid.NewGuid().ToString("N"), //Need to set this to something - will not be used though Username = membershipUser.UserName, - StartContentId = -1, - StartMediaId = -1, IsLockedOut = false, IsApproved = true }; diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index a9f0bda805..076c7b093a 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -42,7 +42,7 @@ namespace Umbraco.Web.Trees { var node = base.CreateRootNode(queryStrings); //if the user's start node is not default, then ensure the root doesn't have a menu - if (Security.CurrentUser.StartContentId != Constants.System.Root) + if (Security.CurrentUser.StartContentIds.Length > 0 && Security.CurrentUser.StartContentIds.Contains(Constants.System.Root) == false) { node.MenuUrl = ""; } @@ -60,9 +60,9 @@ namespace Umbraco.Web.Trees get { return Services.ContentService.RecycleBinSmells(); } } - protected override int UserStartNode + protected override int[] UserStartNodes { - get { return Security.CurrentUser.StartContentId; } + get { return Security.CurrentUser.StartContentIds; } } /// @@ -121,7 +121,7 @@ namespace Umbraco.Web.Trees var menu = new MenuItemCollection(); //if the user's start node is not the root then ensure the root menu is empty/doesn't exist - if (Security.CurrentUser.StartContentId != Constants.System.Root) + if (Security.CurrentUser.StartContentIds.Length > 0 && Security.CurrentUser.StartContentIds.Contains(Constants.System.Root) == false) { return menu; } diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 97c5daed96..f8e8bec293 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -68,7 +68,7 @@ namespace Umbraco.Web.Trees /// /// Returns the user's start node for this tree /// - protected abstract int UserStartNode { get; } + protected abstract int[] UserStartNodes { get; } /// /// Gets the tree nodes for the given id @@ -105,7 +105,7 @@ namespace Umbraco.Web.Trees // Therefore, in the latter case, we want to change the id to -1 since we want to render the current user's root node // and the GetChildEntities method will take care of rendering the correct root node. // If it is in dialog mode, then we don't need to change anything and the children will just render as per normal. - if (IsDialog(queryStrings) == false && UserStartNode != Constants.System.Root) + if (IsDialog(queryStrings) == false && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) { id = Constants.System.Root.ToString(CultureInfo.InvariantCulture); } @@ -139,17 +139,11 @@ namespace Umbraco.Web.Trees //if a request is made for the root node data but the user's start node is not the default, then - // we need to return their start node data - if (iid == Constants.System.Root && UserStartNode != Constants.System.Root) + // we need to return their start nodes + if (iid == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) { - //just return their single start node, it will show up under the 'Content' label - var startNode = Services.EntityService.Get(UserStartNode, UmbracoObjectType); - if (startNode != null) - return new[] { startNode }; - else - { - throw new EntityNotFoundException(UserStartNode, "User's start content node could not be found"); - } + var nodes = Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes); + return nodes; } return Services.EntityService.GetChildren(iid, UmbracoObjectType).ToArray(); @@ -175,7 +169,7 @@ namespace Umbraco.Web.Trees protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { //check if we're rendering the root - if (id == Constants.System.Root.ToInvariantString() && UserStartNode == Constants.System.Root) + if (id == Constants.System.Root.ToInvariantString() && (UserStartNodes.Length == 0 || UserStartNodes.Contains(Constants.System.Root))) { var altStartId = string.Empty; diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index ed72857a40..068d6641b0 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -35,7 +35,7 @@ namespace Umbraco.Web.Trees { var node = base.CreateRootNode(queryStrings); //if the user's start node is not default, then ensure the root doesn't have a menu - if (Security.CurrentUser.StartMediaId != Constants.System.Root) + if (Security.CurrentUser.StartMediaIds.Length > 0 && Security.CurrentUser.StartMediaIds.Contains(Constants.System.Root) == false) { node.MenuUrl = ""; } @@ -53,9 +53,9 @@ namespace Umbraco.Web.Trees get { return Services.MediaService.RecycleBinSmells(); } } - protected override int UserStartNode + protected override int[] UserStartNodes { - get { return Security.CurrentUser.StartMediaId; } + get { return Security.CurrentUser.StartMediaIds; } } /// @@ -100,7 +100,7 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { //if the user's start node is not the root then ensure the root menu is empty/doesn't exist - if (Security.CurrentUser.StartMediaId != Constants.System.Root) + if (Security.CurrentUser.StartMediaIds.Length > 0 && Security.CurrentUser.StartMediaIds.Contains(Constants.System.Root) == false) { return menu; } diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs index 3feb3b682a..3ae70ac5e8 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs @@ -44,9 +44,9 @@ namespace Umbraco.Web.WebApi.Filters FilterBasedOnPermissions(items, user, ApplicationContext.Current.Services.UserService); } - protected override int GetUserStartNode(IUser user) + protected override int[] GetUserStartNodes(IUser user) { - return user.StartContentId; + return user.StartContentIds; } protected override int RecycleBinId diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 36170627d9..72cea5c2d0 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -39,9 +39,9 @@ namespace Umbraco.Web.WebApi.Filters get { return true; } } - protected virtual int GetUserStartNode(IUser user) + protected virtual int[] GetUserStartNodes(IUser user) { - return user.StartMediaId; + return user.StartMediaIds; } protected virtual int RecycleBinId @@ -85,8 +85,8 @@ namespace Umbraco.Web.WebApi.Filters var toRemove = new List(); foreach (dynamic item in items) { - var hasPathAccess = (item != null && UserExtensions.HasPathAccess(item.Path, GetUserStartNode(user), RecycleBinId)); - if (!hasPathAccess) + var hasPathAccess = (item != null && UserExtensions.HasPathAccess(item.Path, GetUserStartNodes(user), RecycleBinId)); + if (hasPathAccess == false) { toRemove.Add(item); } diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index a49c086be3..eb43857b45 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -294,8 +294,8 @@ namespace umbraco.BasePages RealName = u.Name, //currently we only have one user type! Roles = u.GetGroups(), - StartContentNode = u.StartNodeId, - StartMediaNode = u.StartMediaId, + StartContentNodes = u.UserEntity.StartContentIds, + StartMediaNodes = u.UserEntity.StartMediaIds, Username = u.LoginName, Culture = ui.Culture(u) diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 3a425ddef3..94d4802e48 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -707,39 +707,38 @@ namespace umbraco.BusinessLogic UserEntity.IsApproved = value == false; } } - - /// - /// - /// Gets or sets the start content node id. - /// - /// The start node id. + + [Obsolete("This should not be used, it will return invalid data because a user can have multiple start nodes, this will only return the first")] public int StartNodeId { get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return UserEntity.StartContentId; + return UserEntity.StartContentIds == null ? -1 : UserEntity.StartContentIds[0]; } set { - UserEntity.StartContentId = value; + if (UserEntity.StartContentIds == null) + UserEntity.StartContentIds = new int[] {value}; + else if (UserEntity.StartContentIds.Contains(value) == false) + UserEntity.StartContentIds = UserEntity.StartContentIds.Concat(new[] {value}).ToArray(); } } - /// - /// Gets or sets the start media id. - /// - /// The start media id. + [Obsolete("This should not be used, it will return invalid data because a user can have multiple start nodes, this will only return the first")] public int StartMediaId { get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return UserEntity.StartMediaId; + return UserEntity.StartMediaIds == null ? -1 : UserEntity.StartMediaIds[0]; } set { - UserEntity.StartMediaId = value; + if (UserEntity.StartMediaIds == null) + UserEntity.StartMediaIds = new int[] { value }; + else if (UserEntity.StartMediaIds.Contains(value) == false) + UserEntity.StartMediaIds = UserEntity.StartMediaIds.Concat(new[] { value }).ToArray(); } } From 59872b0e17c3fb79334e407b7321f4425ef25e3f Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 May 2017 02:11:38 +1000 Subject: [PATCH 117/510] fixes ability to create new users with the legacy editor --- src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml | 8 -------- src/Umbraco.Web.UI/umbraco/config/create/UI.xml | 2 +- src/umbraco.businesslogic/User.cs | 4 ++-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml index 1cb5e32201..26aaddb78f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml @@ -43,14 +43,6 @@ - -
    User
    - /create/user.ascx - - - - -
    User
    /create/simple.ascx diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index af2eece2ab..265c5b5642 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -43,7 +43,7 @@
    - +
    User
    /create/user.ascx diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 94d4802e48..75fa3d903d 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -714,7 +714,7 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return UserEntity.StartContentIds == null ? -1 : UserEntity.StartContentIds[0]; + return UserEntity.StartContentIds == null || UserEntity.StartContentIds.Length == 0 ? -1 : UserEntity.StartContentIds[0]; } set { @@ -731,7 +731,7 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return UserEntity.StartMediaIds == null ? -1 : UserEntity.StartMediaIds[0]; + return UserEntity.StartMediaIds == null || UserEntity.StartMediaIds.Length == 0 ? -1 : UserEntity.StartMediaIds[0]; } set { From 2cd23cb24f0771bf2eda7a8a4b3d0e81e5151caa Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 May 2017 02:46:21 +1000 Subject: [PATCH 118/510] Updates business logic to persist the start nodes for users --- .../Models/Rdbms/UserStartNodeDto.cs | 2 +- .../Persistence/Factories/UserFactory.cs | 4 +- .../Repositories/UserRepository.cs | 51 ++++++++++++++++++- src/umbraco.businesslogic/User.cs | 10 +--- 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs b/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs index e872111829..42f3e5a7ca 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("startNodeType")] [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.UniqueNonClustered, ForColumns = "startNodeType, startNode", Name = "IX_umbracoUserStartNode_startNodeType")] + [Index(IndexTypes.UniqueNonClustered, ForColumns = "startNodeType, startNode, userId", Name = "IX_umbracoUserStartNode_startNodeType")] public int StartNodeType { get; set; } public enum StartNodeTypeValue diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index e7f2aba08b..c4154a7908 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -32,8 +32,8 @@ namespace Umbraco.Core.Persistence.Factories user.DisableChangeTracking(); user.Key = guidId; - user.StartContentIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.Id).ToArray(); - user.StartMediaIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int) UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.Id).ToArray(); + user.StartContentIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.StartNode).ToArray(); + user.StartMediaIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int) UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.StartNode).ToArray(); user.IsLockedOut = dto.NoConsole; user.IsApproved = dto.Disabled == false; user.Language = dto.UserLanguage; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 4b05b5a47a..4f3fb1b6e7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -204,6 +204,18 @@ namespace Umbraco.Core.Persistence.Repositories var id = Convert.ToInt32(Database.Insert(userDto)); entity.Id = id; + if (entity.IsPropertyDirty("StartContentIds") || entity.IsPropertyDirty("StartMediaIds")) + { + if (entity.IsPropertyDirty("StartContentIds")) + { + AddingOrUpdateStartNodes(entity, Enumerable.Empty(), UserStartNodeDto.StartNodeTypeValue.Content, entity.StartContentIds); + } + if (entity.IsPropertyDirty("StartMediaIds")) + { + AddingOrUpdateStartNodes(entity, Enumerable.Empty(), UserStartNodeDto.StartNodeTypeValue.Media, entity.StartMediaIds); + } + } + if (entity.IsPropertyDirty("Groups")) { //lookup all assigned @@ -228,7 +240,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistUpdatedItem(IUser entity) { //Updates Modified date - ((User)entity).UpdatingEntity(); + ((User)entity).UpdatingEntity(); //ensure security stamp if non if (entity.SecurityStamp.IsNullOrWhiteSpace()) @@ -287,8 +299,21 @@ namespace Umbraco.Core.Persistence.Repositories Database.Update(userDto, changedCols); } + if (entity.IsPropertyDirty("StartContentIds") || entity.IsPropertyDirty("StartMediaIds")) + { + var assignedStartNodes = Database.Fetch("SELECT * FROM umbracoUserStartNode WHERE userId = @userId", new { userId = entity.Id }); + if (entity.IsPropertyDirty("StartContentIds")) + { + AddingOrUpdateStartNodes(entity, assignedStartNodes, UserStartNodeDto.StartNodeTypeValue.Content, entity.StartContentIds); + } + if (entity.IsPropertyDirty("StartMediaIds")) + { + AddingOrUpdateStartNodes(entity, assignedStartNodes, UserStartNodeDto.StartNodeTypeValue.Media, entity.StartMediaIds); + } + } + if (entity.IsPropertyDirty("Groups")) - { + { //lookup all assigned var assigned = entity.Groups == null || entity.Groups.Any() == false ? new List() @@ -312,6 +337,28 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } + private void AddingOrUpdateStartNodes(IEntity entity, IEnumerable current, UserStartNodeDto.StartNodeTypeValue startNodeType, int[] entityStartIds) + { + var assignedIds = current.Where(x => x.StartNodeType == (int)startNodeType).Select(x => x.StartNode).ToArray(); + + //remove the ones not assigned to the entity + var toDelete = assignedIds.Except(entityStartIds).ToArray(); + if (toDelete.Length > 0) + Database.Delete("WHERE UserId = @UserId AND startNode IN (@startNodes)", new {UserId = entity.Id, startNodes = toDelete}); + //add the ones not currently in the db + var toAdd = entityStartIds.Except(assignedIds).ToArray(); + foreach (var i in toAdd) + { + var dto = new UserStartNodeDto + { + StartNode = i, + StartNodeType = (int)startNodeType, + UserId = entity.Id + }; + Database.Insert(dto); + } + } + #endregion #region Implementation of IUserRepository diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 75fa3d903d..114c77e340 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -718,10 +718,7 @@ namespace umbraco.BusinessLogic } set { - if (UserEntity.StartContentIds == null) - UserEntity.StartContentIds = new int[] {value}; - else if (UserEntity.StartContentIds.Contains(value) == false) - UserEntity.StartContentIds = UserEntity.StartContentIds.Concat(new[] {value}).ToArray(); + UserEntity.StartContentIds = new int[] { value }; } } @@ -735,10 +732,7 @@ namespace umbraco.BusinessLogic } set { - if (UserEntity.StartMediaIds == null) - UserEntity.StartMediaIds = new int[] { value }; - else if (UserEntity.StartMediaIds.Contains(value) == false) - UserEntity.StartMediaIds = UserEntity.StartMediaIds.Concat(new[] { value }).ToArray(); + UserEntity.StartMediaIds = new int[] { value }; } } From e9c917e60b1658b3b575a6a4f1bcfdecba435cfc Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 24 May 2017 22:35:47 +0200 Subject: [PATCH 119/510] add resource for getUserGroups --- .../src/common/resources/users.resource.js | 48 +++---------------- 1 file changed, 6 insertions(+), 42 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 35f85063e0..66a292d516 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -141,49 +141,13 @@ return deferred.promise; } - //TODO: Change this over to the real resource function getUserGroups() { - var deferred = $q.defer(); - var userGroups = [ - { - "name": "Admin", - "alias": "admin", - "id": 1, - "icon": "icon-medal", - "sections": [], - "startNodesContent": [], - "startNodesMedia": [] - }, - { - "name": "Writer", - "alias": "writer", - "id": 2, - "icon": "icon-edit", - "sections": [{ "id": "1", "name": "Content" }, { "id": "2", "name": "Media" }], - "startNodesContent": [], - "startNodesMedia": [] - }, - { - "name": "Editor", - "alias": "editor", - "id": 3, - "icon": "icon-tools", - "sections": [{ "id": "1", "name": "Content" }, { "id": "2", "name": "Media" }], - "startNodesContent": [{ "id": "1", "name": "Start node 1" }, { "id": "2", "name": "Start node 2" }], - "startNodesMedia": [{ "id": "1", "name": "Start node 1" }, { "id": "2", "name": "Start node 2" }] - }, - { - "name": "Translator", - "alias": "translator", - "id": 4, - "icon": "icon-globe", - "sections": [{ "id": "1", "name": "Content" }, { "id": "2", "name": "Translations" }], - "startNodesContent": [{ "id": "1", "name": "Start node 1" }], - "startNodesMedia": [{ "id": "1", "name": "Start node 1" }] - } - ]; - deferred.resolve(userGroups); - return deferred.promise; + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "GetUserGroups")), + "Failed to retrieve user groups"); } var resource = { From b95ba2e9a7e4b3264ad73962347c9c7bc767f3f1 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 24 May 2017 22:36:44 +0200 Subject: [PATCH 120/510] wire up user group picker --- .../common/overlays/usergrouppicker/usergrouppicker.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/usergrouppicker/usergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/usergrouppicker/usergrouppicker.html index 9f239a11f0..110465101b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/usergrouppicker/usergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/usergrouppicker/usergrouppicker.html @@ -40,7 +40,7 @@ Content start node: {{ startNode.name }}, - Content root + Content root @@ -48,7 +48,7 @@ Media start node: {{ startNode.name }}, - Media root + Media root From 939d1286627fb43bab00bfeab67a3e3fca79d8e6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 24 May 2017 22:43:51 +0200 Subject: [PATCH 121/510] fix load indicator on user --- .../src/views/users/user.controller.js | 7 +------ src/Umbraco.Web.UI.Client/src/views/users/user.html | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index ecb0eced69..c559994fb9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -5,7 +5,6 @@ var vm = this; - vm.loading = false; vm.page = {}; vm.user = {}; vm.breadcrumbs = []; @@ -28,12 +27,8 @@ usersResource.getUser($routeParams.id).then(function (user) { vm.user = user; makeBreadcrumbs(vm.user); - }); - - // fake loading - $timeout(function () { vm.loading = false; - }, 500); + }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.html b/src/Umbraco.Web.UI.Client/src/views/users/user.html index 32c4ea14c0..a703a45139 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.html @@ -15,7 +15,7 @@ -
    +
    From 911fdf20a4ededcaad31abc675356f86adaec985 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 24 May 2017 22:53:25 +0200 Subject: [PATCH 122/510] add watcher to avatar name --- .../common/directives/components/umbavatar.directive.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js index 51c8d660db..5c2fdb594c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js @@ -53,7 +53,8 @@ Use this directive to render an avatar. function AvatarDirective() { function link(scope, element, attrs, ctrl) { - + + var eventBindings = []; scope.initials = ""; function onInit() { @@ -68,6 +69,12 @@ Use this directive to render an avatar. } } + eventBindings.push(scope.$watch('name', function (newValue, oldValue) { + if (newValue === oldValue) { return; } + if (oldValue === undefined || newValue === undefined) { return; } + scope.initials = getNameInitials(newValue); + })); + onInit(); } From cb097f11a18f65de6b657b2b75e6c026c26e9719 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 25 May 2017 00:13:11 +0200 Subject: [PATCH 123/510] add save user and create user --- .../src/common/resources/users.resource.js | 37 +- .../src/views/users/user.controller.js | 31 +- .../users/views/users/users.controller.js | 34 +- .../src/views/users/views/users/users.html | 646 +++++++++--------- 4 files changed, 421 insertions(+), 327 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 66a292d516..158640cff0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -100,7 +100,34 @@ "GetById", { id: userId })), "Failed to retrieve data for user " + userId); + } + function createUser(user) { + if (!user) { + throw "user not specified"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostCreateUser"), + user), + "Failed to save user"); + } + + function saveUser(user) { + if (!user) { + throw "user not specified"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostSaveUser"), + user), + "Failed to save user"); } function getUserGroup() { @@ -151,12 +178,14 @@ } var resource = { - getUser: getUser, - getUserGroup: getUserGroup, - getUserGroups: getUserGroups, disableUsers: disableUsers, enableUsers: enableUsers, - getPagedResults: getPagedResults + getPagedResults: getPagedResults, + getUser: getUser, + createUser: createUser, + saveUser: saveUser, + getUserGroup: getUserGroup, + getUserGroups: getUserGroups }; return resource; diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index c559994fb9..c69fe3ad6e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -1,9 +1,10 @@ (function () { "use strict"; - function UserEditController($scope, $timeout, $location, usersResource, $routeParams) { + function UserEditController($scope, $timeout, $location, $routeParams, usersResource, contentEditingHelper, localizationService, notificationsService) { var vm = this; + var localizeSaving = localizationService.localize("general_saving"); vm.page = {}; vm.user = {}; @@ -18,6 +19,7 @@ vm.resetPassword = resetPassword; vm.getUserStateType = getUserStateType; vm.changeAvatar = changeAvatar; + vm.save = save; function init() { @@ -32,6 +34,33 @@ } + function save() { + + vm.page.saveButtonState = "busy"; + + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: usersResource.saveUser, + scope: $scope, + content: vm.user, + // We do not redirect on failure for users - this is because it is not possible to actually save a user + // when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + rebindCallback: function (orignal, saved) {} + }).then(function (saved) { + + vm.user = saved; + vm.page.saveButtonState = "success"; + + }, function (err) { + + vm.page.saveButtonState = "error"; + + }); + + } + function goToPage(ancestor) { $location.path(ancestor.path).search("subview", ancestor.subView); } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 10db1fb908..81cb3737e5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -1,9 +1,10 @@ (function () { "use strict"; - function UsersController($scope, $timeout, $location, usersResource) { + function UsersController($scope, $timeout, $location, usersResource, localizationService, contentEditingHelper) { var vm = this; + var localizeSaving = localizationService.localize("general_saving"); vm.users = []; vm.userGroups = []; @@ -69,6 +70,7 @@ vm.areAllSelected = areAllSelected; vm.searchUsers = searchUsers; vm.setOrderByFilter = setOrderByFilter; + vm.createUser = createUser; function init() { @@ -206,6 +208,34 @@ getUsers(); } + function createUser() { + + vm.newUser.id = -1; + vm.newUser.parentId = -1; + vm.page.createButtonState = "busy"; + + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: usersResource.createUser, + scope: $scope, + content: vm.newUser, + // We do not redirect on failure for users - this is because it is not possible to actually save a user + // when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + rebindCallback: function (orignal, saved) {} + }).then(function (saved) { + + vm.page.createButtonState = "success"; + + }, function (err) { + + vm.page.createButtonState = "error"; + + }); + + } + // helpers function getUsers() { @@ -225,7 +255,7 @@ formatDates(vm.users); vm.loading = false; - + }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html index e4b9532ec0..3571719d8e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html @@ -1,353 +1,359 @@
    - +
    - -
    - - + + + +
    - - - - - + + + + + + + - - - - - - -
    /// public IEnumerable GetUserGroups() - { - return Mapper.Map< IEnumerable, IEnumerable>(Services.UserService.GetAllUserGroups()); + { + return Mapper.Map, IEnumerable>(Services.UserService.GetAllUserGroups()); } /// @@ -111,7 +122,7 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); } - + var existing = Services.UserService.GetByEmail(userSave.Email); if (existing != null) { @@ -177,7 +188,7 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); } - + var intId = userSave.Id.TryConvertTo(); if (intId.Success == false) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -189,28 +200,32 @@ namespace Umbraco.Web.Editors var hasErrors = false; var existing = Services.UserService.GetByEmail(userSave.Email); - if (existing != null && existing.Id != (int)userSave.Id) + if (existing != null && existing.Id != userSave.Id) { ModelState.AddModelError("Email", "A user with the email already exists"); hasErrors = true; } existing = Services.UserService.GetByUsername(userSave.Name); - if (existing != null && existing.Id != (int)userSave.Id) + if (existing != null && existing.Id != userSave.Id) { ModelState.AddModelError("Email", "A user with the email already exists"); hasErrors = true; - } + } if (hasErrors) throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); //TODO: More validation, password changing logic, persisting - var user = Mapper.Map(userSave); + //merge the save data onto the user + var user = Mapper.Map(userSave, found); Services.UserService.Save(user); - return Mapper.Map(user); + var display = Mapper.Map(user); + + display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/operationSavedHeader"), Services.TextService.Localize("speechBubbles/editUserSaved")); + return display; } /// @@ -222,7 +237,7 @@ namespace Umbraco.Web.Editors var users = Services.UserService.GetUsersById(userIds).ToArray(); foreach (var u in users) { - u.IsApproved = false; + u.IsApproved = false; } Services.UserService.Save(users); @@ -238,7 +253,7 @@ namespace Umbraco.Web.Editors var users = Services.UserService.GetUsersById(userIds).ToArray(); foreach (var u in users) { - u.IsApproved = true; + u.IsApproved = true; } Services.UserService.Save(users); diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs index e4bbe7850b..29b95e75eb 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs @@ -19,6 +19,9 @@ namespace Umbraco.Web.Models.ContentEditing Notifications = new List(); } + [DataMember(Name = "username")] + public string Username { get; set; } + /// /// The MD5 lowercase hash of the email which can be used by gravatar /// diff --git a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs index 8f42dbf169..8e7b247b23 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs @@ -17,7 +17,15 @@ namespace Umbraco.Web.Models.ContentEditing { //TODO: There will be more information to save along with the structure for changing passwords - [DataMember(Name = "locale", IsRequired = true)] + [DataMember(Name = "id", IsRequired = true)] + [Required] + public new int Id { get; set; } + + [DataMember(Name = "username", IsRequired = true)] + [Required] + public string Username { get; set; } + + [DataMember(Name = "culture", IsRequired = true)] [Required] public string Culture { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 96d52f53fd..731d9037f3 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -15,7 +15,35 @@ namespace Umbraco.Web.Models.Mapping internal class UserModelMapper : MapperConfiguration { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { + { + //Used for merging existing UserSave to an existing IUser instance - this will not create an IUser instance! + config.CreateMap() + .ForMember(user => user.Language, expression => expression.MapFrom(save => save.Culture)) + .ForMember(user => user.SessionTimeout, expression => expression.Ignore()) + .ForMember(user => user.SecurityStamp, expression => expression.Ignore()) + .ForMember(user => user.ProviderUserKey, expression => expression.Ignore()) + .ForMember(user => user.RawPasswordValue, expression => expression.Ignore()) + .ForMember(user => user.PasswordQuestion, expression => expression.Ignore()) + .ForMember(user => user.RawPasswordAnswerValue, expression => expression.Ignore()) + .ForMember(user => user.Comments, expression => expression.Ignore()) + .ForMember(user => user.IsApproved, expression => expression.Ignore()) + .ForMember(user => user.IsLockedOut, expression => expression.Ignore()) + .ForMember(user => user.LastLoginDate, expression => expression.Ignore()) + .ForMember(user => user.LastPasswordChangeDate, expression => expression.Ignore()) + .ForMember(user => user.LastLockoutDate, expression => expression.Ignore()) + .ForMember(user => user.FailedPasswordAttempts, expression => expression.Ignore()) + .ForMember(user => user.DeletedDate, expression => expression.Ignore()) + .ForMember(user => user.CreateDate, expression => expression.Ignore()) + .ForMember(user => user.UpdateDate, expression => expression.Ignore()) + .AfterMap((save, user) => + { + user.ClearGroups(); + foreach (var group in save.UserGroups) + { + user.AddGroup(group); + } + }); + config.CreateMap() .ConstructUsing(invite => new User(invite.Name, invite.Email, invite.Email, Guid.NewGuid().ToString("N"))) .ForMember(user => user.Id, expression => expression.Ignore()) @@ -57,7 +85,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(detail => detail.Path, opt => opt.MapFrom(user => "-1," + user.Id)) .ForMember(detail => detail.AdditionalData, opt => opt.Ignore()); - config.CreateMap() + config.CreateMap() + .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups)) .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 114c77e340..54748f7637 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -648,10 +648,7 @@ namespace umbraco.BusinessLogic public void ClearGroups() { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - foreach (var group in UserEntity.Groups.ToArray()) - { - UserEntity.RemoveGroup(group); - } + UserEntity.ClearGroups(); } /// From 528be48437e5e4272adcb64159ef5cacaf458350 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 26 May 2017 00:02:32 +1000 Subject: [PATCH 125/510] Adds UsersController.SetAvatar and ClearAvatar --- src/Umbraco.Core/Models/Membership/IUser.cs | 5 + src/Umbraco.Core/Models/Membership/User.cs | 9 + src/Umbraco.Core/Models/Rdbms/UserDto.cs | 7 + .../Persistence/Factories/UserFactory.cs | 4 +- .../src/common/resources/users.resource.js | 9 +- .../src/common/services/util.service.js | 165 +++++++++--------- src/Umbraco.Web/Editors/UsersController.cs | 83 ++++++++- .../Models/ContentEditing/UserSave.cs | 5 +- .../Models/Mapping/UserModelMapper.cs | 2 + 9 files changed, 201 insertions(+), 88 deletions(-) diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index c679181963..d6133c04dc 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -37,5 +37,10 @@ namespace Umbraco.Core.Models.Membership /// The security stamp used by ASP.Net identity /// string SecurityStamp { get; set; } + + /// + /// Will hold the media file system relative path of the users custom avatar if they uploaded one + /// + string Avatar { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 9c444b48e4..e3c4d910af 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -95,6 +95,7 @@ namespace Umbraco.Core.Models.Membership private string _name; private string _securityStamp; + private string _avatar; private int _sessionTimeout; private int[] _startContentIds; private int[] _startMediaIds; @@ -124,6 +125,7 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastPasswordChangeDate); public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); + public readonly PropertyInfo AvatarSelector = ExpressionHelper.GetPropertyInfo(x => x.Avatar); public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentIds); public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaIds); @@ -272,6 +274,13 @@ namespace Umbraco.Core.Models.Membership set { SetPropertyValueAndDetectChanges(value, ref _securityStamp, Ps.Value.SecurityStampSelector); } } + [DataMember] + public string Avatar + { + get { return _avatar; } + set { SetPropertyValueAndDetectChanges(value, ref _avatar, Ps.Value.AvatarSelector); } + } + /// /// Gets or sets the session timeout. /// diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Models/Rdbms/UserDto.cs index 0f74a92b7b..98dee12a94 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserDto.cs @@ -79,6 +79,13 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.NotNull)] [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime UpdateDate { get; set; } + + /// + /// Will hold the media file system relative path of the users custom avatar if they uploaded one + /// + [Column("avatar")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Avatar { get; set; } [ResultColumn] public List UserGroupDtos { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index c4154a7908..64a63447d9 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -44,6 +44,7 @@ namespace Umbraco.Core.Persistence.Factories user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue; user.CreateDate = dto.CreateDate; user.UpdateDate = dto.UpdateDate; + user.Avatar = dto.Avatar; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -74,7 +75,8 @@ namespace Umbraco.Core.Persistence.Factories LastLoginDate = entity.LastLoginDate == DateTime.MinValue ? (DateTime?)null : entity.LastLoginDate, LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, CreateDate = entity.CreateDate, - UpdateDate = entity.UpdateDate + UpdateDate = entity.UpdateDate, + Avatar = entity.Avatar }; foreach (var startNodeId in entity.StartContentIds) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 158640cff0..c70729246f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -9,7 +9,7 @@ (function () { 'use strict'; - function usersResource($http, umbRequestHelper, $q) { + function usersResource($http, umbRequestHelper, $q, umbDataFormatter) { function disableUsers(userIds) { if (!userIds) { @@ -121,15 +121,18 @@ throw "user not specified"; } + //need to convert the user data into the correctly formatted save data - it is *not* the same and we don't want to over-post + var formattedSaveData = umbDataFormatter.formatUserPostData(user); + return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "userApiBaseUrl", "PostSaveUser"), - user), + formattedSaveData), "Failed to save user"); } - + function getUserGroup() { var deferred = $q.defer(); var user = { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index ba395becfc..f093d4bb31 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -4,7 +4,7 @@ function versionHelper() { return { //see: https://gist.github.com/TheDistantSea/8021359 - versionCompare: function(v1, v2, options) { + versionCompare: function (v1, v2, options) { var lexicographical = options && options.lexicographical, zeroExtend = options && options.zeroExtend, v1parts = v1.split('.'), @@ -61,15 +61,15 @@ angular.module('umbraco.services').factory('versionHelper', versionHelper); function dateHelper() { return { - - convertToServerStringTime: function(momentLocal, serverOffsetMinutes, format) { + + convertToServerStringTime: function (momentLocal, serverOffsetMinutes, format) { //get the formatted offset time in HH:mm (server time offset is in minutes) var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + moment() - .startOf('day') - .minutes(Math.abs(serverOffsetMinutes)) - .format('HH:mm'); + .startOf('day') + .minutes(Math.abs(serverOffsetMinutes)) + .format('HH:mm'); var server = moment.utc(momentLocal).utcOffset(formattedOffset); return server.format(format ? format : "YYYY-MM-DD HH:mm:ss"); @@ -80,9 +80,9 @@ function dateHelper() { //get the formatted offset time in HH:mm (server time offset is in minutes) var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + moment() - .startOf('day') - .minutes(Math.abs(serverOffsetMinutes)) - .format('HH:mm'); + .startOf('day') + .minutes(Math.abs(serverOffsetMinutes)) + .format('HH:mm'); //convert to the iso string format var isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; @@ -121,30 +121,30 @@ angular.module('umbraco.services').factory('packageHelper', packageHelper); function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, mediaHelper, umbRequestHelper) { return { /** sets the image's url, thumbnail and if its a folder */ - setImageData: function(img) { - + setImageData: function (img) { + img.isFolder = !mediaHelper.hasFilePropertyType(img); - if(!img.isFolder){ + if (!img.isFolder) { img.thumbnail = mediaHelper.resolveFile(img, true); - img.image = mediaHelper.resolveFile(img, false); + img.image = mediaHelper.resolveFile(img, false); } }, /** sets the images original size properties - will check if it is a folder and if so will just make it square */ - setOriginalSize: function(img, maxHeight) { + setOriginalSize: function (img, maxHeight) { //set to a square by default img.originalWidth = maxHeight; img.originalHeight = maxHeight; - var widthProp = _.find(img.properties, function(v) { return (v.alias === "umbracoWidth"); }); + var widthProp = _.find(img.properties, function (v) { return (v.alias === "umbracoWidth"); }); if (widthProp && widthProp.value) { img.originalWidth = parseInt(widthProp.value, 10); if (isNaN(img.originalWidth)) { img.originalWidth = maxHeight; } } - var heightProp = _.find(img.properties, function(v) { return (v.alias === "umbracoHeight"); }); + var heightProp = _.find(img.properties, function (v) { return (v.alias === "umbracoHeight"); }); if (heightProp && heightProp.value) { img.originalHeight = parseInt(heightProp.value, 10); if (isNaN(img.originalHeight)) { @@ -154,7 +154,7 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me }, /** sets the image style which get's used in the angular markup */ - setImageStyle: function(img, width, height, rightMargin, bottomMargin) { + setImageStyle: function (img, width, height, rightMargin, bottomMargin) { img.style = { width: width + "px", height: height + "px", "margin-right": rightMargin + "px", "margin-bottom": bottomMargin + "px" }; img.thumbStyle = { "background-image": "url('" + img.thumbnail + "')", @@ -162,10 +162,10 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me "background-position": "center", "background-size": Math.min(width, img.originalWidth) + "px " + Math.min(height, img.originalHeight) + "px" }; - }, + }, /** gets the image's scaled wdith based on the max row height */ - getScaledWidth: function(img, maxHeight) { + getScaledWidth: function (img, maxHeight) { var scaled = img.originalWidth * maxHeight / img.originalHeight; return scaled; //round down, we don't want it too big even by half a pixel otherwise it'll drop to the next row @@ -173,7 +173,7 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me }, /** returns the target row width taking into account how many images will be in the row and removing what the margin is */ - getTargetWidth: function(imgsPerRow, maxRowWidth, margin) { + getTargetWidth: function (imgsPerRow, maxRowWidth, margin) { //take into account the margin, we will have 1 less margin item than we have total images return (maxRowWidth - ((imgsPerRow - 1) * margin)); }, @@ -187,7 +187,7 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me targetHeight = optional; */ - getRowHeightForImages: function(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, targetHeight) { + getRowHeightForImages: function (imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, targetHeight) { var idealImages = imgs.slice(0, idealImgPerRow); //get the target row width without margin @@ -195,8 +195,8 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me //this gets the image with the smallest height which equals the maximum we can scale up for this image block var maxScaleableHeight = this.getMaxScaleableHeight(idealImages, maxRowHeight); //if the max scale height is smaller than the min display height, we'll use the min display height - targetHeight = targetHeight !== undefined ? targetHeight : Math.max(maxScaleableHeight, minDisplayHeight); - + targetHeight = targetHeight !== undefined ? targetHeight : Math.max(maxScaleableHeight, minDisplayHeight); + var attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight); if (attemptedRowHeight != null) { @@ -206,12 +206,12 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me if (attemptedRowHeight < minDisplayHeight) { if (idealImages.length > 1) { - + //we'll generate a new targetHeight that is halfway between the max and the current and recurse, passing in a new targetHeight targetHeight += Math.floor((maxRowHeight - targetHeight) / 2); return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin, targetHeight); } - else { + else { //this will occur when we only have one image remaining in the row but it's still going to be too wide even when // using the minimum display height specified. In this case we're going to have to just crop the image in it's center // using the minimum display height and the full row width @@ -241,7 +241,7 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me //if we're already dealing with the ideal images per row and it's not quite wide enough, we can scale up a little bit so // long as the targetHeight is currently less than the maxRowHeight. The scale up will be half-way between our current // target height and the maxRowHeight (we won't loop forever though - if there's a difference of 5 px we'll just quit) - + while (targetHeight < maxRowHeight && (maxRowHeight - targetHeight) > 5) { targetHeight += Math.floor((maxRowHeight - targetHeight) / 2); attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight); @@ -273,7 +273,7 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me }, - performGetRowHeight: function(idealImages, targetRowWidth, minDisplayHeight, targetHeight) { + performGetRowHeight: function (idealImages, targetRowWidth, minDisplayHeight, targetHeight) { var currRowWidth = 0; @@ -285,7 +285,7 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me if (currRowWidth > targetRowWidth) { //get the new scaled height to fit var newHeight = targetRowWidth * targetHeight / currRowWidth; - + return newHeight; } else if (idealImages.length === 1 && (currRowWidth <= targetRowWidth) && !idealImages[0].isFolder) { @@ -303,7 +303,7 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me }, /** builds an image grid row */ - buildRow: function(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, totalRemaining) { + buildRow: function (imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, totalRemaining) { var currRowWidth = 0; var row = { images: [] }; @@ -315,11 +315,11 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me for (var i = 0; i < imageRowHeight.imgCount; i++) { //get the lower width to ensure it always fits var scaledWidth = Math.floor(this.getScaledWidth(imgs[i], imageRowHeight.height)); - + if (currRowWidth + scaledWidth <= targetWidth) { - currRowWidth += scaledWidth; + currRowWidth += scaledWidth; sizes.push({ - width:scaledWidth, + width: scaledWidth, //ensure that the height is rounded height: Math.round(imageRowHeight.height) }); @@ -352,17 +352,17 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me if (row.images.length === 1 && totalRemaining > 1) { //if there's only one image on the row and there are more images remaining, set the container to max width - row.images[0].style.width = maxRowWidth + "px"; + row.images[0].style.width = maxRowWidth + "px"; } - + return row; }, /** Returns the maximum image scaling height for the current image collection */ - getMaxScaleableHeight: function(imgs, maxRowHeight) { + getMaxScaleableHeight: function (imgs, maxRowHeight) { - var smallestHeight = _.min(imgs, function(item) { return item.originalHeight; }).originalHeight; + var smallestHeight = _.min(imgs, function (item) { return item.originalHeight; }).originalHeight; //adjust the smallestHeight if it is larger than the static max row height if (smallestHeight > maxRowHeight) { @@ -372,10 +372,10 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me }, /** Creates the image grid with calculated widths/heights for images to fill the grid nicely */ - buildGrid: function(images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin,imagesOnly) { + buildGrid: function (images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin, imagesOnly) { var rows = []; - var imagesProcessed = 0; + var imagesProcessed = 0; //first fill in all of the original image sizes and URLs for (var i = startingIndex; i < images.length; i++) { @@ -384,7 +384,7 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me this.setImageData(item); this.setOriginalSize(item, maxRowHeight); - if(imagesOnly && !item.isFolder && !item.thumbnail){ + if (imagesOnly && !item.isFolder && !item.thumbnail) { images.splice(i, 1); i--; } @@ -449,7 +449,7 @@ function umbModelMapper() { /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */ convertToEntityBasic: function (source) { - var required = ["id", "name", "icon", "parentId", "path"]; + var required = ["id", "name", "icon", "parentId", "path"]; _.each(required, function (k) { if (!_.has(source, k)) { throw "The source object does not contain the property " + k; @@ -485,11 +485,11 @@ function umbSessionStorage($window) { get: function (key) { return angular.fromJson(storage["umb_" + key]); }, - - set : function(key, value) { + + set: function (key, value) { storage["umb_" + key] = angular.toJson(value); } - + }; } angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorage); @@ -504,26 +504,26 @@ angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorag */ function updateChecker($http, umbRequestHelper) { return { - - /** - * @ngdoc function - * @name umbraco.services.updateChecker#check - * @methodOf umbraco.services.updateChecker - * @function - * - * @description - * Called to load in the legacy tree js which is required on startup if a user is logged in or - * after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. - */ - check: function() { - + + /** + * @ngdoc function + * @name umbraco.services.updateChecker#check + * @methodOf umbraco.services.updateChecker + * @function + * + * @description + * Called to load in the legacy tree js which is required on startup if a user is logged in or + * after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. + */ + check: function () { + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "updateCheckApiBaseUrl", - "GetCheck")), - 'Failed to retrieve update status'); - } + $http.get( + umbRequestHelper.getApiUrl( + "updateCheckApiBaseUrl", + "GetCheck")), + 'Failed to retrieve update status'); + } }; } angular.module('umbraco.services').factory('updateChecker', updateChecker); @@ -546,7 +546,7 @@ function umbPropEditorHelper() { * * @param {string} input the view path currently stored for the property editor */ - getViewPath: function(input, isPreValue) { + getViewPath: function (input, isPreValue) { var path = String(input); if (path.startsWith('/')) { @@ -582,7 +582,7 @@ angular.module('umbraco.services').factory('umbPropEditorHelper', umbPropEditorH **/ function umbDataFormatter() { return { - + formatContentTypePostData: function (displayModel, action) { //create the save model from the display model @@ -594,7 +594,7 @@ function umbDataFormatter() { //TODO: Map these saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; }); saveModel.defaultTemplate = displayModel.defaultTemplate ? displayModel.defaultTemplate.alias : null; - var realGroups = _.reject(displayModel.groups, function(g) { + var realGroups = _.reject(displayModel.groups, function (g) { //do not include these tabs return g.tabState === "init"; }); @@ -621,9 +621,9 @@ function umbDataFormatter() { return saveGroup; }); - + //we don't want any null groups - saveModel.groups = _.reject(saveModel.groups, function(g) { + saveModel.groups = _.reject(saveModel.groups, function (g) { return !g; }); @@ -631,7 +631,7 @@ function umbDataFormatter() { }, /** formats the display model used to display the data type to the model used to save the data type */ - formatDataTypePostData: function(displayModel, preValues, action) { + formatDataTypePostData: function (displayModel, preValues, action) { var saveModel = { parentId: displayModel.parentId, id: displayModel.id, @@ -651,14 +651,23 @@ function umbDataFormatter() { return saveModel; }, + /** formats the display model used to display the user to the model used to save the user */ + formatUserPostData: function (displayModel, preValues, action) { + + //create the save model from the display model + var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups'); + + return saveModel; + }, + /** formats the display model used to display the member to the model used to save the member */ - formatMemberPostData: function(displayModel, action) { + formatMemberPostData: function (displayModel, action) { //this is basically the same as for media but we need to explicitly add the username,email, password to the save model var saveModel = this.formatMediaPostData(displayModel, action); saveModel.key = displayModel.key; - + var genericTab = _.find(displayModel.tabs, function (item) { return item.id === 0; }); @@ -679,7 +688,7 @@ function umbDataFormatter() { saveModel.email = propEmail.value; saveModel.username = propLogin.value; saveModel.password = propPass.value; - + var selectedGroups = []; for (var n in propGroups.value) { if (propGroups.value[n] === true) { @@ -687,12 +696,12 @@ function umbDataFormatter() { } } saveModel.memberGroups = selectedGroups; - + //turn the dictionary into an array of pairs var memberProviderPropAliases = _.pairs(displayModel.fieldConfig); _.each(displayModel.tabs, function (tab) { _.each(tab.properties, function (prop) { - var foundAlias = _.find(memberProviderPropAliases, function(item) { + var foundAlias = _.find(memberProviderPropAliases, function (item) { return prop.alias === item[1]; }); if (foundAlias) { @@ -709,7 +718,7 @@ function umbDataFormatter() { saveModel.comments = prop.value; break; } - } + } }); }); @@ -719,7 +728,7 @@ function umbDataFormatter() { }, /** formats the display model used to display the media to the model used to save the media */ - formatMediaPostData: function(displayModel, action) { + formatMediaPostData: function (displayModel, action) { //NOTE: the display model inherits from the save model so we can in theory just post up the display model but // we don't want to post all of the data as it is unecessary. var saveModel = { @@ -744,7 +753,7 @@ function umbDataFormatter() { value: prop.value }); } - + }); }); @@ -760,8 +769,8 @@ function umbDataFormatter() { var genericTab = _.find(displayModel.tabs, function (item) { return item.id === 0; }); - - var propExpireDate = _.find(genericTab.properties, function(item) { + + var propExpireDate = _.find(genericTab.properties, function (item) { return item.alias === "_umb_expiredate"; }); var propReleaseDate = _.find(genericTab.properties, function (item) { diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 0e74de8af1..9bb136b31c 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -1,14 +1,17 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Threading.Tasks; using System.Web; using System.Web.Http; using AutoMapper; using ClientDependency.Core; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -42,13 +45,89 @@ namespace Umbraco.Web.Editors { } - public IHttpActionResult SetAvatar(int id) + [FileUploadCleanupFilter(false)] + public async Task SetAvatar(int id) { - return Ok(); + if (Request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + + var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var result = await Request.Content.ReadAsMultipartAsync(provider); + + //must have a file + if (result.FileData.Count == 0) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //get the string json from the request + var userId = result.FormData["userId"]; + int intUserId; + if (int.TryParse(userId, out intUserId) == false) + return Request.CreateValidationErrorResponse("The request was not formatted correctly, the userId is not an integer"); + + var user = Services.UserService.GetUserById(intUserId); + if (user == null) + return Request.CreateResponse(HttpStatusCode.NotFound); + + var tempFiles = new PostedFiles(); + + if (result.FileData.Count > 1) + return Request.CreateValidationErrorResponse("The request was not formatted correctly, only one file can be attached to the request"); + + //get the file info + var file = result.FileData[0]; + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); + var safeFileName = fileName.ToSafeFileName(); + var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); + + if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) + { + if (user.Avatar.IsNullOrWhiteSpace()) + { + //we'll need to generate a new path! + //make it a hash of known data, we don't want this path to be guessable + user.Avatar = "UserAvatars/" + (user.Id + user.CreateDate.ToString("yyyyMMdd")).ToSHA1() + ext; + } + + using (var fs = System.IO.File.OpenRead(file.LocalFileName)) + { + FileSystemProviderManager.Current.MediaFileSystem.AddFile(user.Avatar, fs, true); + } + + Services.UserService.Save(user); + + //track the temp file so the cleanup filter removes it + tempFiles.UploadedFiles.Add(new ContentItemFile + { + TempFilePath = file.LocalFileName + }); + } + + return Request.CreateResponse(HttpStatusCode.OK, tempFiles); } public IHttpActionResult ClearAvatar(int id) { + var found = Services.UserService.GetUserById(id); + if (found == null) + return NotFound(); + + var filePath = found.Avatar; + + found.Avatar = null; + + Services.UserService.Save(found); + + if (FileSystemProviderManager.Current.MediaFileSystem.FileExists(filePath)) + FileSystemProviderManager.Current.MediaFileSystem.DeleteFile(filePath); + return Ok(); } diff --git a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs index 8e7b247b23..4b408d324e 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs @@ -42,10 +42,7 @@ namespace Umbraco.Web.Models.ContentEditing public int[] StartContentIds { get; set; } [DataMember(Name = "startMediaIds")] - public int[] StartMediaIds { get; set; } - - [DataMember(Name = "allowedSections")] - public IEnumerable AllowedSections { get; set; } + public int[] StartMediaIds { get; set; } public IEnumerable Validate(ValidationContext validationContext) { diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 731d9037f3..aee0ae8336 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -19,6 +19,7 @@ namespace Umbraco.Web.Models.Mapping //Used for merging existing UserSave to an existing IUser instance - this will not create an IUser instance! config.CreateMap() .ForMember(user => user.Language, expression => expression.MapFrom(save => save.Culture)) + .ForMember(user => user.Avatar, expression => expression.Ignore()) .ForMember(user => user.SessionTimeout, expression => expression.Ignore()) .ForMember(user => user.SecurityStamp, expression => expression.Ignore()) .ForMember(user => user.ProviderUserKey, expression => expression.Ignore()) @@ -47,6 +48,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ConstructUsing(invite => new User(invite.Name, invite.Email, invite.Email, Guid.NewGuid().ToString("N"))) .ForMember(user => user.Id, expression => expression.Ignore()) + .ForMember(user => user.Avatar, expression => expression.Ignore()) .ForMember(user => user.SessionTimeout, expression => expression.Ignore()) .ForMember(user => user.StartContentIds, expression => expression.Ignore()) .ForMember(user => user.StartMediaIds, expression => expression.Ignore()) From 8aabb3e3d004ff3eb9dd6db87b03c40b2c8ba41d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 26 May 2017 02:15:37 +1000 Subject: [PATCH 126/510] wires up custom user avatars --- src/Umbraco.Core/Models/UserExtensions.cs | 66 +++++++++++++ .../Repositories/UserRepository.cs | 3 +- .../src/common/resources/users.resource.js | 14 ++- .../src/controllers/main.controller.js | 41 +++----- .../views/install-local.controller.js | 2 +- .../src/views/users/user.controller.js | 97 +++++++++++++++---- .../src/views/users/user.html | 30 ++++-- .../src/views/users/views/users/users.html | 4 +- .../Editors/BackOfficeController.cs | 6 +- src/Umbraco.Web/Editors/GravatarController.cs | 38 -------- src/Umbraco.Web/Editors/UsersController.cs | 43 ++++---- .../Models/ContentEditing/UserDetail.cs | 6 ++ .../Models/ContentEditing/UserDisplay.cs | 7 +- .../Models/Mapping/UserModelMapper.cs | 5 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 15 files changed, 237 insertions(+), 126 deletions(-) delete mode 100644 src/Umbraco.Web/Editors/GravatarController.cs diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 4894be7b87..6636259c92 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -1,6 +1,9 @@ using System; using System.Globalization; using System.Linq; +using System.Net; +using Umbraco.Core.Cache; +using Umbraco.Core.IO; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -8,6 +11,69 @@ namespace Umbraco.Core.Models { public static class UserExtensions { + /// + /// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL + /// + /// + /// + /// + /// + /// A list of 5 different sized avatar URLs + /// + internal static string[] GetCurrentUserAvatarUrls(this IUser user, IUserService userService, ICacheProvider staticCache) + { + if (user.Avatar.IsNullOrWhiteSpace()) + { + var gravatarHash = user.Email.ToMd5(); + var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash; + + //try gravatar + var gravatarAccess = staticCache.GetCacheItem("UserAvatar" + user.Id, () => + { + // Test if we can reach this URL, will fail when there's network or firewall errors + var request = (HttpWebRequest)WebRequest.Create(gravatarUrl); + // Require response within 10 seconds + request.Timeout = 10000; + try + { + using ((HttpWebResponse)request.GetResponse()) { } + } + catch (Exception) + { + // There was an HTTP or other error, return an null instead + return false; + } + return true; + }); + + if (gravatarAccess) + { + return new[] + { + gravatarUrl + "?s=30&d=mm", + gravatarUrl + "?s=60&d=mm", + gravatarUrl + "?s=90&d=mm", + gravatarUrl + "?s=150&d=mm", + gravatarUrl + "?s=300&d=mm" + }; + } + + return null; + } + + //use the custom avatar + var avatarUrl = FileSystemProviderManager.Current.MediaFileSystem.GetUrl(user.Avatar); + return new[] + { + avatarUrl + "?width=30&height=30&mode=crop", + avatarUrl + "?width=60&height=60&mode=crop", + avatarUrl + "?width=90&height=90&mode=crop", + avatarUrl + "?width=150&height=150&mode=crop", + avatarUrl + "?width=300&height=300&mode=crop" + }; + + } + /// /// Returns the culture info associated with this user, based on the language they're assigned to in the back office /// diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 4f3fb1b6e7..296985f084 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -269,7 +269,8 @@ namespace Umbraco.Core.Persistence.Repositories {"lastLoginDate", "LastLoginDate"}, {"failedLoginAttempts", "FailedPasswordAttempts"}, {"createDate", "CreateDate"}, - {"updateDate", "UpdateDate"} + {"updateDate", "UpdateDate"}, + {"avatar", "Avatar"} }; //create list of properties that have changed diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index c70729246f..c08fbb0cdf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -11,6 +11,17 @@ function usersResource($http, umbRequestHelper, $q, umbDataFormatter) { + function clearAvatar(userId) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostClearAvatar", + { id: userId })), + 'Failed to clear the user avatar ' + userId); + } + function disableUsers(userIds) { if (!userIds) { throw "userIds not specified"; @@ -188,7 +199,8 @@ createUser: createUser, saveUser: saveUser, getUserGroup: getUserGroup, - getUserGroups: getUserGroups + getUserGroups: getUserGroups, + clearAvatar: clearAvatar }; return resource; diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js index 1504abf7c1..f17fefcac8 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -60,7 +60,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ $scope.authenticated = data.authenticated; $scope.user = data.user; - updateChecker.check().then(function(update) { + updateChecker.check().then(function (update) { if (update && update !== "null") { if (update.type !== "None") { var notification = { @@ -87,7 +87,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ } //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data - if (data.loginType === "credentials") { + if (data.loginType === "credentials") { localStorageService.clearAll(); } @@ -96,30 +96,15 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ tmhDynamicLocale.set($scope.user.locale); } - if ($scope.user.emailHash) { + if ($scope.user.avatars) { - //let's attempt to load the avatar, it might not exist or we might not have - // internet access, well get an empty string back - $http.get(umbRequestHelper.getApiUrl("gravatarApiBaseUrl", "GetCurrentUserGravatarUrl")) - .then( - function successCallback(response) { - // if we can't download the gravatar for some reason, an null gets returned, we cannot do anything - if (response.data !== "null") { - if ($scope.user && $scope.user.emailHash) { - var avatarBaseUrl = "https://www.gravatar.com/avatar/"; - var hash = $scope.user.emailHash; + $scope.avatar = []; + if (angular.isArray($scope.user.avatars)) { + for (var i = 0; i < $scope.user.avatars.length; i++) { + $scope.avatar.push({ value: $scope.user.avatars[i] }); + } + } - $scope.avatar = [ - { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" } - ]; - } - } - - }, function errorCallback(response) { - //cannot load it from the server so we cannot do anything - }); } })); @@ -143,7 +128,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ //register it angular.module('umbraco').controller("Umbraco.MainController", MainController). - config(function (tmhDynamicLocaleProvider) { - //Set url for locale files - tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js'); - }); + config(function (tmhDynamicLocaleProvider) { + //Set url for locale files + tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js'); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js index 9ce2506e76..b540df524c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -96,7 +96,7 @@ } } else if (evt.Message) { - file.serverErrorMessage = evt.Message; + vm.zipFile.serverErrorMessage = evt.Message; } } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index c69fe3ad6e..888b14b93f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function UserEditController($scope, $timeout, $location, $routeParams, usersResource, contentEditingHelper, localizationService, notificationsService) { + function UserEditController($scope, $timeout, $location, $routeParams, usersResource, contentEditingHelper, localizationService, notificationsService, mediaHelper, Upload, umbRequestHelper) { var vm = this; var localizeSaving = localizationService.localize("general_saving"); @@ -9,6 +9,7 @@ vm.page = {}; vm.user = {}; vm.breadcrumbs = []; + vm.avatarFile = {}; vm.goToPage = goToPage; vm.openUserGroupPicker = openUserGroupPicker; @@ -18,8 +19,10 @@ vm.disableUser = disableUser; vm.resetPassword = resetPassword; vm.getUserStateType = getUserStateType; - vm.changeAvatar = changeAvatar; + vm.clearAvatar = clearAvatar; vm.save = save; + vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB" + vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); function init() { @@ -31,7 +34,7 @@ makeBreadcrumbs(vm.user); vm.loading = false; }); - + } function save() { @@ -47,7 +50,7 @@ // when server side validation fails - as opposed to content where we are capable of saving the content // item if server side validation fails redirectOnFailure: false, - rebindCallback: function (orignal, saved) {} + rebindCallback: function (orignal, saved) { } }).then(function (saved) { vm.user = saved; @@ -72,17 +75,17 @@ selection: vm.user.userGroups, closeButtonLabel: "Cancel", show: true, - submit: function(model) { + submit: function (model) { // apply changes - if(model.selection) { + if (model.selection) { vm.user.userGroups = model.selection; } vm.userGroupPicker.show = false; vm.userGroupPicker = null; }, - close: function(oldModel) { + close: function (oldModel) { // rollback on close - if(oldModel.selection) { + if (oldModel.selection) { vm.user.userGroups = oldModel.selection; } vm.userGroupPicker.show = false; @@ -97,14 +100,14 @@ view: "contentpicker", multiPicker: true, show: true, - submit: function(model) { - if(model.selection) { + submit: function (model) { + if (model.selection) { vm.user.startNodesContent = model.selection; } vm.contentPicker.show = false; vm.contentPicker = null; }, - close: function(oldModel) { + close: function (oldModel) { vm.contentPicker.show = false; vm.contentPicker = null; } @@ -120,14 +123,14 @@ entityType: "media", multiPicker: true, show: true, - submit: function(model) { - if(model.selection) { + submit: function (model) { + if (model.selection) { vm.user.startNodesMedia = model.selection; } vm.contentPicker.show = false; vm.contentPicker = null; }, - close: function(oldModel) { + close: function (oldModel) { vm.contentPicker.show = false; vm.contentPicker = null; } @@ -144,6 +147,13 @@ function resetPassword() { alert("reset password"); + } + + function clearAvatar() { + // get user + usersResource.clearAvatar(vm.user.id).then(function (data) { + vm.user.avatars = data; + }); } function getUserStateType(state) { @@ -157,10 +167,63 @@ } } - function changeAvatar() { - alert("change avatar"); + $scope.changeAvatar = function (files, event) { + if (files && files.length > 0) { + upload(files[0]); + } + }; + + function upload(file) { + + Upload.upload({ + url: umbRequestHelper.getApiUrl("userApiBaseUrl", "PostSetAvatar", { id: vm.user.id }), + fields: {}, + file: file + }).progress(function (evt) { + + //TODO: Do progress, etc... + // set uploading status on file + vm.avatarFile.uploadStatus = "uploading"; + + }).success(function (data, status, headers, config) { + + // set done status on file + vm.avatarFile.uploadStatus = "done"; + + vm.user.avatars = data; + + }).error(function (evt, status, headers, config) { + + // set status done + vm.avatarFile.uploadStatus = "error"; + + // If file not found, server will return a 404 and display this message + if (status === 404) { + vm.avatarFile.serverErrorMessage = "File not found"; + } + else if (status == 400) { + //it's a validation error + vm.avatarFile.serverErrorMessage = evt.message; + } + else { + //it's an unhandled error + //if the service returns a detailed error + if (evt.InnerException) { + vm.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + vm.avatarFile.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + vm.avatarFile.serverErrorMessage = evt.Message; + } + } + }); } + function makeBreadcrumbs() { vm.breadcrumbs = [ { @@ -173,7 +236,7 @@ } ]; } - + init(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.html b/src/Umbraco.Web.UI.Client/src/views/users/user.html index a703a45139..0c6ded037a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.html @@ -119,18 +119,30 @@
    - + + - - - + img-src="{{vm.user.avatars[4]}}"> + + + Change avatar +
    + + Clear avatar + +
    Last login:
    @@ -181,10 +178,11 @@
    @@ -192,9 +190,10 @@
    From 271c771f1c78c5ed25f15c9d3257a09df3d2e2ea Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 26 May 2017 09:50:51 +1000 Subject: [PATCH 128/510] fixing tests --- src/Umbraco.Core/Models/Membership/User.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index e3c4d910af..5bb954ec81 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -73,12 +73,14 @@ namespace Umbraco.Core.Models.Membership /// public User(int id, string name, string email, string username, string rawPasswordValue, IEnumerable allowedSections, IEnumerable userGroups) : this() - { + { + //we allow whitespace for this value so just check null + if (rawPasswordValue == null) throw new ArgumentNullException("rawPasswordValue"); if (allowedSections == null) throw new ArgumentNullException("allowedSections"); if (userGroups == null) throw new ArgumentNullException("userGroups"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); - if (string.IsNullOrWhiteSpace(rawPasswordValue)) throw new ArgumentException("Value cannot be null or whitespace.", "rawPasswordValue"); + Id = id; _name = name; From 4337036358ad6bd51d8aff7b8388f30748f52d08 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 26 May 2017 11:17:06 +1000 Subject: [PATCH 129/510] refactor users and how groups are added to them so we can know dynamically start nodes, etc... fixes tests --- .../Models/Membership/IReadOnlyUserGroup.cs | 20 ++++++ src/Umbraco.Core/Models/Membership/IUser.cs | 4 +- .../Models/Membership/IUserGroup.cs | 9 +-- .../Models/Membership/ReadOnlyUserGroup.cs | 51 +++++++++++++++ src/Umbraco.Core/Models/Membership/User.cs | 54 ++++++++-------- .../Models/Membership/UserGroupExtensions.cs | 18 ++++++ src/Umbraco.Core/Models/UserExtensions.cs | 14 +++-- .../Persistence/Factories/UserFactory.cs | 18 +----- .../Repositories/UserRepository.cs | 4 +- .../Security/BackOfficeUserStore.cs | 19 +++--- src/Umbraco.Core/Services/UserService.cs | 4 +- src/Umbraco.Core/Umbraco.Core.csproj | 3 + .../Repositories/UserRepositoryTest.cs | 62 ++++++++++++++++--- .../UmbracoBackOfficeIdentityTests.cs | 6 +- .../Services/ContentServiceTests.cs | 2 +- .../Services/UserServiceTests.cs | 6 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 8 +-- .../ContentControllerUnitTests.cs | 2 +- ...terAllowedOutgoingContentAttributeTests.cs | 2 +- .../MediaControllerUnitTests.cs | 2 +- .../Controllers/UsersControllerTests.cs | 20 +----- .../Models/Mapping/UserModelMapper.cs | 26 +++----- src/umbraco.businesslogic/User.cs | 6 +- 23 files changed, 232 insertions(+), 128 deletions(-) create mode 100644 src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs create mode 100644 src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs create mode 100644 src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs rename src/Umbraco.Tests/Web/Controllers/{WebApiEditors => }/ContentControllerUnitTests.cs (99%) rename src/Umbraco.Tests/Web/Controllers/{WebApiEditors => }/FilterAllowedOutgoingContentAttributeTests.cs (98%) rename src/Umbraco.Tests/Web/Controllers/{WebApiEditors => }/MediaControllerUnitTests.cs (98%) rename src/Umbraco.Tests/{ => Web}/Controllers/UsersControllerTests.cs (93%) diff --git a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs new file mode 100644 index 0000000000..2498e1f70e --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Membership +{ + /// + /// A readonly user group providing basic information + /// + public interface IReadOnlyUserGroup + { + int StartContentId { get; } + int StartMediaId { get; } + + /// + /// The alias + /// + string Alias { get; } + + IEnumerable AllowedSections { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index d6133c04dc..d6af54f937 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -20,11 +20,11 @@ namespace Umbraco.Core.Models.Membership /// /// Gets the groups that user is part of /// - IEnumerable Groups { get; } + IEnumerable Groups { get; } void RemoveGroup(string group); void ClearGroups(); - void AddGroup(string group); + void AddGroup(IReadOnlyUserGroup group); IEnumerable AllowedSections { get; } diff --git a/src/Umbraco.Core/Models/Membership/IUserGroup.cs b/src/Umbraco.Core/Models/Membership/IUserGroup.cs index d2c58883a2..106f52f569 100644 --- a/src/Umbraco.Core/Models/Membership/IUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IUserGroup.cs @@ -5,6 +5,8 @@ namespace Umbraco.Core.Models.Membership { public interface IUserGroup : IAggregateRoot { + string Alias { get; set; } + int StartContentId { get; set; } int StartMediaId { get; set; } @@ -12,12 +14,7 @@ namespace Umbraco.Core.Models.Membership /// The icon ///
    string Icon { get; set; } - - /// - /// The alias - /// - string Alias { get; set; } - + /// /// The name /// diff --git a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs new file mode 100644 index 0000000000..68c2f094b9 --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Membership +{ + public class ReadOnlyUserGroup : IReadOnlyUserGroup, IEquatable + { + public ReadOnlyUserGroup(int startContentId, int startMediaId, string @alias, IEnumerable allowedSections) + { + StartContentId = startContentId; + StartMediaId = startMediaId; + Alias = alias; + AllowedSections = allowedSections; + } + + public int StartContentId { get; private set; } + public int StartMediaId { get; private set; } + public string Alias { get; private set; } + public IEnumerable AllowedSections { get; private set; } + + public bool Equals(ReadOnlyUserGroup other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Alias, other.Alias); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ReadOnlyUserGroup) obj); + } + + public override int GetHashCode() + { + return Alias.GetHashCode(); + } + + public static bool operator ==(ReadOnlyUserGroup left, ReadOnlyUserGroup right) + { + return Equals(left, right); + } + + public static bool operator !=(ReadOnlyUserGroup left, ReadOnlyUserGroup right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 5bb954ec81..db55622118 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Models.Membership public User() { SessionTimeout = 60; - _userGroups = new List(); + _userGroups = new HashSet(); _language = GlobalSettings.DefaultUILanguage; _isApproved = true; _isLockedOut = false; @@ -31,7 +31,6 @@ namespace Umbraco.Core.Models.Membership _startMediaIds = new int[] { }; //cannot be null _rawPasswordValue = ""; - _allowedSections = new List(); } /// @@ -53,8 +52,7 @@ namespace Umbraco.Core.Models.Membership _email = email; _username = username; _rawPasswordValue = rawPasswordValue; - _allowedSections = new List(); - _userGroups = new List(); + _userGroups = new HashSet(); _isApproved = true; _isLockedOut = false; _startContentIds = new int[] { }; @@ -69,26 +67,22 @@ namespace Umbraco.Core.Models.Membership /// /// /// - /// /// - public User(int id, string name, string email, string username, string rawPasswordValue, IEnumerable allowedSections, IEnumerable userGroups) + public User(int id, string name, string email, string username, string rawPasswordValue, IEnumerable userGroups) : this() { //we allow whitespace for this value so just check null if (rawPasswordValue == null) throw new ArgumentNullException("rawPasswordValue"); - if (allowedSections == null) throw new ArgumentNullException("allowedSections"); if (userGroups == null) throw new ArgumentNullException("userGroups"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); - + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); Id = id; _name = name; _email = email; _username = username; _rawPasswordValue = rawPasswordValue; - _allowedSections = allowedSections; - _userGroups = new List(userGroups); + _userGroups = new HashSet(userGroups); _isApproved = true; _isLockedOut = false; _startContentIds = new int[] { }; @@ -107,7 +101,7 @@ namespace Umbraco.Core.Models.Membership private string _email; private string _rawPasswordValue; private IEnumerable _allowedSections; - private List _userGroups; + private HashSet _userGroups; private bool _isApproved; private bool _isLockedOut; private string _language; @@ -142,7 +136,7 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); - public readonly PropertyInfo UserGroupsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Groups); + public readonly PropertyInfo UserGroupsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Groups); //Custom comparer for enumerable public readonly DelegateEqualityComparer> IntegerEnumerableComparer = @@ -258,7 +252,7 @@ namespace Umbraco.Core.Models.Membership public IEnumerable AllowedSections { - get { return _allowedSections; } + get { return _allowedSections ?? (_allowedSections = new List(_userGroups.SelectMany(x => x.AllowedSections).Distinct())); } } public IProfile ProfileData @@ -339,20 +333,25 @@ namespace Umbraco.Core.Models.Membership } /// - /// Gets or sets the groups that user is part of + /// Gets the groups that user is part of /// [DataMember] - public IEnumerable Groups + public IEnumerable Groups { get { return _userGroups; } } public void RemoveGroup(string group) { - if (_userGroups.Contains(group)) + foreach (var userGroup in _userGroups.ToArray()) { - _userGroups.Remove(group); - OnPropertyChanged(Ps.Value.UserGroupsSelector); + if (userGroup.Alias == group) + { + _userGroups.Remove(userGroup); + //reset this flag so it's rebuilt with the assigned groups + _allowedSections = null; + OnPropertyChanged(Ps.Value.UserGroupsSelector); + } } } @@ -361,17 +360,20 @@ namespace Umbraco.Core.Models.Membership if (_userGroups.Count > 0) { _userGroups.Clear(); + //reset this flag so it's rebuilt with the assigned groups + _allowedSections = null; OnPropertyChanged(Ps.Value.UserGroupsSelector); } } - public void AddGroup(string group) + public void AddGroup(IReadOnlyUserGroup group) { - if (_userGroups.Contains(group) == false) - { - _userGroups.Add(group); + if (_userGroups.Add(group)) + { + //reset this flag so it's rebuilt with the assigned groups + _allowedSections = null; OnPropertyChanged(Ps.Value.UserGroupsSelector); - } + } } #endregion @@ -385,8 +387,8 @@ namespace Umbraco.Core.Models.Membership //turn off change tracking clone.DisableChangeTracking(); //need to create new collections otherwise they'll get copied by ref - clone._userGroups = new List(_userGroups); - clone._allowedSections = new List(_allowedSections); + clone._userGroups = new HashSet(_userGroups); + clone._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; //re-create the event handler //this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs new file mode 100644 index 0000000000..ec39fb0bf4 --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs @@ -0,0 +1,18 @@ +using Umbraco.Core.Models.Rdbms; +using System.Linq; + +namespace Umbraco.Core.Models.Membership +{ + internal static class UserGroupExtensions + { + public static IReadOnlyUserGroup ToReadOnlyGroup(this IUserGroup group) + { + return new ReadOnlyUserGroup(group.StartContentId, group.StartMediaId, group.Alias, group.AllowedSections); + } + + public static IReadOnlyUserGroup ToReadOnlyGroup(this UserGroupDto group) + { + return new ReadOnlyUserGroup(group.StartContentId, group.StartMediaId, group.Alias, group.UserGroup2AppDtos.Select(x => x.AppAlias).ToArray()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 6636259c92..198c41bd54 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -125,12 +125,16 @@ namespace Umbraco.Core.Models var formattedPath = "," + path + ","; var formattedRecycleBinId = "," + recycleBinId.ToInvariantString() + ","; - //only users with root access have access to the recycle bin + //check for root path access + //TODO: This logic may change + if (startNodeIds.Length == 0 || startNodeIds.Contains(Constants.System.Root)) + return true; + + //only users with root access have access to the recycle bin so if the above check didn't pass than access is denied if (formattedPath.Contains(formattedRecycleBinId)) { - var hasAccess = startNodeIds.Length == 0 || startNodeIds.Contains(Constants.System.Root); - return hasAccess; - } + return false; + } //check for normal paths foreach (var startNodeId in startNodeIds) @@ -168,7 +172,7 @@ namespace Umbraco.Core.Models public static bool IsAdmin(this IUser user) { if (user == null) throw new ArgumentNullException("user"); - return user.Groups != null && user.Groups.Any(x => x == Constants.Security.AdminGroupAlias); + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 64a63447d9..2915bc570e 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -11,21 +11,9 @@ namespace Umbraco.Core.Persistence.Factories { public static IUser BuildEntity(UserDto dto) { - var guidId = dto.Id.ToGuid(); - - var groupAliases = new HashSet(); - var allowedSections = new HashSet(); - var userGroups = dto.UserGroupDtos; - foreach (var userGroup in userGroups) - { - groupAliases.Add(userGroup.Alias); - foreach (var section in userGroup.UserGroup2AppDtos) - { - allowedSections.Add(section.AppAlias); - } - } - - var user = new User(dto.Id, dto.UserName, dto.Email, dto.Login,dto.Password, allowedSections, groupAliases); + var guidId = dto.Id.ToGuid(); + + var user = new User(dto.Id, dto.UserName, dto.Email, dto.Login,dto.Password, dto.UserGroupDtos.Select(x => x.ToReadOnlyGroup()).ToArray()); try { diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 296985f084..96c3575596 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -221,7 +221,7 @@ namespace Umbraco.Core.Persistence.Repositories //lookup all assigned var assigned = entity.Groups == null || entity.Groups.Any() == false ? new List() - : Database.Fetch("SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)", new { aliases = entity.Groups }); + : Database.Fetch("SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)", new { aliases = entity.Groups.Select(x => x.Alias) }); foreach (var groupDto in assigned) { @@ -318,7 +318,7 @@ namespace Umbraco.Core.Persistence.Repositories //lookup all assigned var assigned = entity.Groups == null || entity.Groups.Any() == false ? new List() - : Database.Fetch("SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)", new { aliases = entity.Groups }); + : Database.Fetch("SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)", new { aliases = entity.Groups.Select(x => x.Alias) }); //first delete all //TODO: We could do this a nicer way instead of "Nuke and Pave" diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 611242b088..d8bf69641d 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -408,7 +408,7 @@ namespace Umbraco.Core.Security if (foundUser != null && foundGroup != null) { - foundUser.AddGroup(foundGroup.Alias); + foundUser.AddGroup(foundGroup.ToReadOnlyGroup()); } return Task.FromResult(0); @@ -688,22 +688,21 @@ namespace Umbraco.Core.Security anythingChanged = true; user.SecurityStamp = identityUser.SecurityStamp; } - - if (user.Groups.ContainsAll(identityUser.Groups) == false - || identityUser.Groups.ContainsAll(user.Groups) == false) + + var userGroups = user.Groups.Select(x => x.Alias).ToArray(); + if (userGroups.ContainsAll(identityUser.Groups) == false + || identityUser.Groups.ContainsAll(userGroups) == false) { anythingChanged = true; - + //clear out the current groups (need to ToArray since we are modifying the iterator) - foreach (var group in user.Groups.ToArray()) - { - user.RemoveGroup(group); - } + user.ClearGroups(); + //get all of the ones found by alias and add them var foundGroups = _userService.GetUserGroupsByAlias(identityUser.Groups); foreach (var group in foundGroups) { - user.AddGroup(group.Alias); + user.AddGroup(group.ToReadOnlyGroup()); } } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index a801986b60..dd81824f6a 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -825,7 +825,7 @@ namespace Umbraco.Core.Services foreach (var group in user.Groups) { //TODO: This may perform horribly :/ - foreach (var permission in GetPermissions(group, false, nodeIds)) + foreach (var permission in GetPermissions(group.Alias, false, nodeIds)) { AddOrAmendPermissionList(result, permission); } @@ -922,7 +922,7 @@ namespace Umbraco.Core.Services /// String indicating permissions for provided user and path public string GetPermissionsForPath(IUser user, string path) { - var assignedPermissions = GetPermissionsForGroupsAndPath(user.Groups, path); + var assignedPermissions = GetPermissionsForGroupsAndPath(user.Groups.Select(x => x.Alias), path); return GetAggregatePermissions(assignedPermissions); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 52a367224b..3b6caadbcb 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -343,6 +343,9 @@ + + + diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index a4cf866cd2..88ecb50f2f 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -2,6 +2,8 @@ using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Querying; @@ -28,6 +30,29 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); } + private MediaRepository CreateMediaRepository(IScopeUnitOfWork unitOfWork, out IMediaTypeRepository mediaTypeRepository) + { + mediaTypeRepository = new MediaTypeRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); + var tagRepository = new TagRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); + var repository = new MediaRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax, mediaTypeRepository, tagRepository, Mock.Of()); + return repository; + } + + private ContentRepository CreateContentRepository(IScopeUnitOfWork unitOfWork, out IContentTypeRepository contentTypeRepository) + { + ITemplateRepository tr; + return CreateContentRepository(unitOfWork, out contentTypeRepository, out tr); + } + + private ContentRepository CreateContentRepository(IScopeUnitOfWork unitOfWork, out IContentTypeRepository contentTypeRepository, out ITemplateRepository templateRepository) + { + templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()); + var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger, SqlSyntax); + contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, templateRepository); + var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository, Mock.Of()); + return repository; + } + private UserRepository CreateRepository(IScopeUnitOfWork unitOfWork) { var repository = new UserRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); @@ -75,7 +100,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { IUser user = MockedUser.CreateUser(); - user.AddGroup(group.Alias); + user.AddGroup(group.ToReadOnlyGroup()); // Act repository.AddOrUpdate(user); @@ -86,7 +111,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(user.HasIdentity, Is.True); Assert.AreEqual(1, user.Groups.Count()); - Assert.AreEqual(group.Alias, user.Groups.ElementAt(0)); + Assert.AreEqual(group.Alias, user.Groups.ElementAt(0).Alias); } } @@ -137,30 +162,49 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Perform_Update_On_UserRepository() { + var ct = MockedContentTypes.CreateBasicContentType("test"); + var content = MockedContent.CreateBasicContent(ct); + var mt = MockedContentTypes.CreateSimpleMediaType("testmedia", "TestMedia"); + var media = MockedMedia.CreateSimpleMedia(mt, "asdf", -1); + // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - using (var repository = CreateRepository(unitOfWork)) + IContentTypeRepository contentTypeRepo; + IMediaTypeRepository mediaTypeRepo; + using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepo)) + using (var mediaRepository = CreateMediaRepository(unitOfWork, out mediaTypeRepo)) + using (contentTypeRepo) + using(mediaTypeRepo) + using (var userRepository = CreateRepository(unitOfWork)) using (var userGroupRepository = CreateUserGroupRepository(unitOfWork)) { - var user = CreateAndCommitUserWithGroup(repository, userGroupRepository, unitOfWork); + contentTypeRepo.AddOrUpdate(ct); + mediaTypeRepo.AddOrUpdate(mt); + unitOfWork.Commit(); + + contentRepository.AddOrUpdate(content); + mediaRepository.AddOrUpdate(media); + unitOfWork.Commit(); + + var user = CreateAndCommitUserWithGroup(userRepository, userGroupRepository, unitOfWork); // Act - var resolved = (User)repository.Get((int)user.Id); + var resolved = (User)userRepository.Get((int)user.Id); resolved.Name = "New Name"; resolved.Language = "fr"; resolved.IsApproved = false; resolved.RawPasswordValue = "new"; resolved.IsLockedOut = true; - resolved.StartContentIds = new []{ 10 }; - resolved.StartMediaIds = new []{ 11 }; + resolved.StartContentIds = new[] { content.Id }; + resolved.StartMediaIds = new[] { media.Id }; resolved.Email = "new@new.com"; resolved.Username = "newName"; - repository.AddOrUpdate(resolved); + userRepository.AddOrUpdate(resolved); unitOfWork.Commit(); - var updatedItem = (User)repository.Get((int)user.Id); + var updatedItem = (User)userRepository.Get((int)user.Id); // Assert Assert.That(updatedItem.Id, Is.EqualTo(resolved.Id)); diff --git a/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs b/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs index 226b26990d..634c2e6f5a 100644 --- a/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs +++ b/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs @@ -29,8 +29,8 @@ namespace Umbraco.Tests.Security //This is the id that 'identity' uses to check for the username new Claim(ClaimTypes.Name, "testing", ClaimValueTypes.String, TestIssuer, TestIssuer), new Claim(ClaimTypes.GivenName, "hello world", ClaimValueTypes.String, TestIssuer, TestIssuer), - new Claim(Constants.Security.StartContentNodeIdClaimType, "-1", ClaimValueTypes.Integer32, TestIssuer, TestIssuer), - new Claim(Constants.Security.StartMediaNodeIdClaimType, "5543", ClaimValueTypes.Integer32, TestIssuer, TestIssuer), + new Claim(Constants.Security.StartContentNodeIdClaimType, "[-1]", ClaimValueTypes.Integer32, TestIssuer, TestIssuer), + new Claim(Constants.Security.StartMediaNodeIdClaimType, "[5543]", ClaimValueTypes.Integer32, TestIssuer, TestIssuer), new Claim(Constants.Security.AllowedApplicationsClaimType, "content", ClaimValueTypes.String, TestIssuer, TestIssuer), new Claim(Constants.Security.AllowedApplicationsClaimType, "media", ClaimValueTypes.String, TestIssuer, TestIssuer), new Claim(ClaimTypes.Locality, "en-us", ClaimValueTypes.String, TestIssuer, TestIssuer), @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Security Assert.AreEqual(sessionId, backofficeIdentity.SessionId); Assert.AreEqual("testing", backofficeIdentity.Username); Assert.AreEqual("hello world", backofficeIdentity.RealName); - Assert.AreEqual(0, backofficeIdentity.StartContentNodes.Length); + Assert.AreEqual(1, backofficeIdentity.StartContentNodes.Length); Assert.IsTrue(backofficeIdentity.StartMediaNodes.UnsortedSequenceEqual(new []{ 5543 })); Assert.IsTrue(new[] {"content", "media"}.SequenceEqual(backofficeIdentity.AllowedApplications)); Assert.AreEqual("en-us", backofficeIdentity.Culture); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 652152273d..1690bfdbee 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1404,7 +1404,7 @@ namespace Umbraco.Tests.Services Assert.IsTrue(ServiceContext.PublicAccessService.AddRule(content1, "test2", "test2").Success); var user = ServiceContext.UserService.GetUserById(0); - var userGroup = ServiceContext.UserService.GetUserGroupByAlias(user.Groups.First()); + var userGroup = ServiceContext.UserService.GetUserGroupByAlias(user.Groups.First().Alias); Assert.IsNotNull(ServiceContext.NotificationService.CreateNotification(user, content1, "test")); ServiceContext.ContentService.AssignContentPermission(content1, 'A', new[] { userGroup.Id}); diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index 5a0ad30169..0ac00d0fb4 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -337,7 +337,7 @@ namespace Umbraco.Tests.Services var users = MockedUser.CreateMulipleUsers(10).ToArray(); for (var i = 0; i < 10;) { - users[i].AddGroup(userGroup.Alias); + users[i].AddGroup(userGroup.ToReadOnlyGroup()); i = i + 2; } ServiceContext.UserService.Save(users); @@ -360,7 +360,7 @@ namespace Umbraco.Tests.Services var users = MockedUser.CreateMulipleUsers(10).ToArray(); for (var i = 0; i < 10;) { - users[i].AddGroup(userGroup.Alias); + users[i].AddGroup(userGroup.ToReadOnlyGroup()); i = i + 2; } for (var i = 0; i < 10;) @@ -697,7 +697,7 @@ namespace Umbraco.Tests.Services userGroup = CreateTestUserGroup(); var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); - user.AddGroup(userGroup.Alias); + user.AddGroup(userGroup.ToReadOnlyGroup()); ServiceContext.UserService.Save(user); return user; } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 651ae301ce..7d5dcc0fd5 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -190,7 +190,7 @@ - + @@ -368,9 +368,9 @@ - - - + + + diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs similarity index 99% rename from src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs rename to src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs index 7782612420..ae735be64e 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Editors; -namespace Umbraco.Tests.Web.Controllers.WebApiEditors +namespace Umbraco.Tests.Web.Controllers { [TestFixture] public class ContentControllerUnitTests diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs b/src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs similarity index 98% rename from src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs rename to src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs index bc4046b39e..b93d503d3d 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs @@ -11,7 +11,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.WebApi.Filters; -namespace Umbraco.Tests.Web.Controllers.WebApiEditors +namespace Umbraco.Tests.Web.Controllers { [TestFixture] public class FilterAllowedOutgoingContentAttributeTests diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs similarity index 98% rename from src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs rename to src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs index e65b1a1b19..7923920fe7 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Editors; -namespace Umbraco.Tests.Web.Controllers.WebApiEditors +namespace Umbraco.Tests.Web.Controllers { [TestFixture] public class MediaControllerUnitTests diff --git a/src/Umbraco.Tests/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs similarity index 93% rename from src/Umbraco.Tests/Controllers/UsersControllerTests.cs rename to src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 7af3d5e340..ad09e241c2 100644 --- a/src/Umbraco.Tests/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -1,23 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Net; +using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; -using System.Net.Http.Headers; -using System.Text; -using System.Web.Configuration; -using System.Web.Http; -using System.Web.Http.SelfHost; -using AutoMapper; -using Examine.Providers; -using Microsoft.Owin.Testing; using Moq; using Newtonsoft.Json; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -26,10 +12,8 @@ using Umbraco.Tests.TestHelpers.ControllerTesting; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; -using Umbraco.Web.WebApi; -namespace Umbraco.Tests.Controllers +namespace Umbraco.Tests.Web.Controllers { [DatabaseTestBehavior(DatabaseBehavior.NoDatabasePerFixture)] [TestFixture] diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 5669bbaac8..81ac54caae 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -40,9 +40,10 @@ namespace Umbraco.Web.Models.Mapping .AfterMap((save, user) => { user.ClearGroups(); - foreach (var group in save.UserGroups) + var foundGroups = applicationContext.Services.UserService.GetUserGroupsByAlias(save.UserGroups.ToArray()); + foreach (var group in foundGroups) { - user.AddGroup(group); + user.AddGroup(group.ToReadOnlyGroup()); } }); @@ -72,10 +73,12 @@ namespace Umbraco.Web.Models.Mapping .ForMember(user => user.UpdateDate, expression => expression.Ignore()) .AfterMap((invite, user) => { - foreach (var group in invite.UserGroups) + user.ClearGroups(); + var foundGroups = applicationContext.Services.UserService.GetUserGroupsByAlias(invite.UserGroups.ToArray()); + foreach (var group in foundGroups) { - user.AddGroup(group); - } + user.AddGroup(group.ToReadOnlyGroup()); + } }); config.CreateMap() @@ -123,18 +126,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember( detail => detail.EmailHash, opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) - .ForMember(detail => detail.SecondsUntilTimeout, opt => opt.Ignore()); - - config.CreateMap() - .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => user.Id)) - .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) - .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) - .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture)) - .ForMember(detail => detail.AllowedSections, opt => opt.MapFrom(user => user.AllowedSections)) - .ForMember( - detail => detail.EmailHash, - opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) - .ForMember(detail => detail.SecondsUntilTimeout, opt => opt.Ignore()); + .ForMember(detail => detail.SecondsUntilTimeout, opt => opt.Ignore()); config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(profile => GetIntId(profile.Id))); diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 54748f7637..fbf226c39f 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -657,7 +657,9 @@ namespace umbraco.BusinessLogic public void AddGroup(string groupAlias) { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - UserEntity.AddGroup(groupAlias); + var group = ApplicationContext.Current.Services.UserService.GetUserGroupByAlias(groupAlias); + if (group != null) + UserEntity.AddGroup(group.ToReadOnlyGroup()); } /// @@ -667,7 +669,7 @@ namespace umbraco.BusinessLogic public string[] GetGroups() { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return UserEntity.Groups.ToArray(); + return UserEntity.Groups.Select(x => x.Alias).ToArray(); } From 362b98509b719af9cd78f6a8fa6e3de6c5102090 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 26 May 2017 12:09:06 +1000 Subject: [PATCH 130/510] Updates path check to use all assigned user start nodes that are combined by their groups and directly assigned values --- src/Umbraco.Core/Models/Membership/User.cs | 12 ++++++--- src/Umbraco.Core/Models/UserExtensions.cs | 25 ++++++++++++++++--- .../Persistence/Factories/UserFactory.cs | 7 +++--- .../Models/Mapping/UserModelMapper.cs | 2 +- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index db55622118..b2e380811b 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -57,7 +57,7 @@ namespace Umbraco.Core.Models.Membership _isLockedOut = false; _startContentIds = new int[] { }; _startMediaIds = new int[] { }; - } + } /// /// Constructor for creating a new User instance for an existing user @@ -68,12 +68,16 @@ namespace Umbraco.Core.Models.Membership /// /// /// - public User(int id, string name, string email, string username, string rawPasswordValue, IEnumerable userGroups) + /// + /// + public User(int id, string name, string email, string username, string rawPasswordValue, IEnumerable userGroups, int[] startContentIds, int[] startMediaIds) : this() { //we allow whitespace for this value so just check null if (rawPasswordValue == null) throw new ArgumentNullException("rawPasswordValue"); if (userGroups == null) throw new ArgumentNullException("userGroups"); + if (startContentIds == null) throw new ArgumentNullException("startContentIds"); + if (startMediaIds == null) throw new ArgumentNullException("startMediaIds"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); @@ -85,8 +89,8 @@ namespace Umbraco.Core.Models.Membership _userGroups = new HashSet(userGroups); _isApproved = true; _isLockedOut = false; - _startContentIds = new int[] { }; - _startMediaIds = new int[] { }; + _startContentIds = startContentIds; + _startMediaIds = startMediaIds; } private string _name; diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 198c41bd54..4209878116 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; @@ -10,7 +11,25 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Models { public static class UserExtensions - { + { + /// + /// Returns all of the user's assigned start node ids based on ids assigned directly to the IUser object and it's groups + /// + /// + public static IEnumerable GetCombinedStartContentIds(this IUser user) + { + return user.StartContentIds.Concat(user.Groups.Select(x => x.StartContentId)).Distinct(); + } + + /// + /// Returns all of the user's assigned start node ids based on ids assigned directly to the IUser object and it's groups + /// + /// + public static IEnumerable GetCombinedMediaContentIds(this IUser user) + { + return user.StartMediaIds.Concat(user.Groups.Select(x => x.StartMediaId)).Distinct(); + } + /// /// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL /// @@ -115,7 +134,7 @@ namespace Umbraco.Core.Models { if (user == null) throw new ArgumentNullException("user"); if (content == null) throw new ArgumentNullException("content"); - return HasPathAccess(content.Path, user.StartContentIds, Constants.System.RecycleBinContent); + return HasPathAccess(content.Path, user.GetCombinedStartContentIds().ToArray(), Constants.System.RecycleBinContent); } internal static bool HasPathAccess(string path, int[] startNodeIds, int recycleBinId) @@ -159,7 +178,7 @@ namespace Umbraco.Core.Models { if (user == null) throw new ArgumentNullException("user"); if (media == null) throw new ArgumentNullException("media"); - return HasPathAccess(media.Path, user.StartMediaIds, Constants.System.RecycleBinMedia); + return HasPathAccess(media.Path, user.GetCombinedMediaContentIds().ToArray(), Constants.System.RecycleBinMedia); } /// diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 2915bc570e..cae42191c6 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -13,15 +13,16 @@ namespace Umbraco.Core.Persistence.Factories { var guidId = dto.Id.ToGuid(); - var user = new User(dto.Id, dto.UserName, dto.Email, dto.Login,dto.Password, dto.UserGroupDtos.Select(x => x.ToReadOnlyGroup()).ToArray()); + var user = new User(dto.Id, dto.UserName, dto.Email, dto.Login,dto.Password, + dto.UserGroupDtos.Select(x => x.ToReadOnlyGroup()).ToArray(), + dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.StartNode).ToArray(), + dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.StartNode).ToArray()); try { user.DisableChangeTracking(); user.Key = guidId; - user.StartContentIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.StartNode).ToArray(); - user.StartMediaIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int) UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.StartNode).ToArray(); user.IsLockedOut = dto.NoConsole; user.IsApproved = dto.Disabled == false; user.Language = dto.UserLanguage; diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 81ac54caae..339de9387e 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -94,7 +94,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.Avatars, opt => opt.MapFrom(user => user.GetCurrentUserAvatarUrls(applicationContext.Services.UserService, applicationContext.ApplicationCache.RuntimeCache))) .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) - .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups)) + .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups.Select(x => x.Alias).ToArray())) .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) From f493c254e802bec45e1cc7b543e10216a8e4bcf8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 26 May 2017 12:47:31 +1000 Subject: [PATCH 131/510] Fixes up the users login date on the user list view, fixes tests --- .../Web/Controllers/UsersControllerTests.cs | 33 ++++++++++++------- .../src/views/users/user.html | 4 +-- .../users/views/users/users.controller.js | 4 +-- .../src/views/users/views/users/users.html | 2 +- .../Models/ContentEditing/UserDisplay.cs | 2 +- .../Models/Mapping/UserModelMapper.cs | 1 + 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index ad09e241c2..32a04350ee 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; using Moq; @@ -36,7 +37,11 @@ namespace Umbraco.Tests.Web.Controllers u.Id = 1234; }); userServiceMock.Setup(service => service.GetAllUserGroups(It.IsAny())) - .Returns(Enumerable.Empty); + .Returns(new[] { Mock.Of(group => group.Id == 123 && group.Alias == "writers" && group.Name == "Writers") }); + userServiceMock.Setup(service => service.GetUserGroupsByAlias(It.IsAny())) + .Returns(new[] { Mock.Of(group => group.Id == 123 && group.Alias == "writers" && group.Name == "Writers") }); + userServiceMock.Setup(service => service.GetUserById(It.IsAny())) + .Returns(new User(1234, "Test", "asdf@asdf.com", "test", "", new List(), new int[0], new int[0])); //we need to manually apply automapper mappings with the mocked applicationcontext InitializeMappers(helper.UmbracoContext.Application); @@ -44,30 +49,32 @@ namespace Umbraco.Tests.Web.Controllers return new UsersController(helper.UmbracoContext); }); - var invite = new UserInvite + var userSave = new UserSave { Id = -1, Email = "test@test.com", - Message = "Hello test!", + Username = "test@test.com", + Culture = "en", Name = "Test", UserGroups = new[] { "writers" } }; var response = await runner.Execute("Users", "PostSaveUser", HttpMethod.Post, - new ObjectContent(invite, new JsonMediaTypeFormatter())); + new ObjectContent(userSave, new JsonMediaTypeFormatter())); var obj = JsonConvert.DeserializeObject(response.Item2); - Assert.AreEqual(invite.Name, obj.Name); + Assert.AreEqual(userSave.Name, obj.Name); Assert.AreEqual(1234, obj.Id); - Assert.AreEqual(invite.Email, obj.Email); - foreach (var group in invite.UserGroups) + Assert.AreEqual(userSave.Email, obj.Email); + foreach (var group in userSave.UserGroups) { Assert.IsTrue(obj.UserGroups.Contains(group)); } } - [Test] - public async void Invite_User() + [TestCase("PostInviteUser")] + [TestCase("PostCreateUser")] + public async void Invite_And_Create(string action) { var runner = new TestRunner((message, helper) => { @@ -82,7 +89,9 @@ namespace Umbraco.Tests.Web.Controllers u.Id = 1234; }); userServiceMock.Setup(service => service.GetAllUserGroups(It.IsAny())) - .Returns(Enumerable.Empty); + .Returns(new[] { Mock.Of(group => group.Id == 123 && group.Alias == "writers" && group.Name == "Writers") }); + userServiceMock.Setup(service => service.GetUserGroupsByAlias(It.IsAny())) + .Returns(new[]{Mock.Of(group => group.Id == 123 && group.Alias == "writers" && group.Name == "Writers")}); //we need to manually apply automapper mappings with the mocked applicationcontext InitializeMappers(helper.UmbracoContext.Application); @@ -98,7 +107,7 @@ namespace Umbraco.Tests.Web.Controllers Name = "Test", UserGroups = new[] {"writers"} }; - var response = await runner.Execute("Users", "PostInviteUser", HttpMethod.Post, + var response = await runner.Execute("Users", action, HttpMethod.Post, new ObjectContent(invite, new JsonMediaTypeFormatter())); var obj = JsonConvert.DeserializeObject(response.Item2); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.html b/src/Umbraco.Web.UI.Client/src/views/users/user.html index a20a985088..840dde1704 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.html @@ -160,8 +160,8 @@
    Last login:
    - {{ vm.user.lastLogin }} - The user has not logged in yet + {{ vm.user.lastLoginDate }} + The user has not logged in yet
    diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 81cb3737e5..3635521fea 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -285,8 +285,8 @@ function formatDates(users) { angular.forEach(users, function(user){ - if(user.lastLogin) { - user.formattedLastLogin = moment(user.lastLogin).format("MMMM Do YYYY, HH:mm"); + if (user.lastLoginDate) { + user.formattedLastLogin = moment(user.lastLoginDate).format("MMMM Do YYYY, HH:mm"); } }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html index 46432010dc..527f72666c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html @@ -140,7 +140,7 @@ diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs index b9bea4bba3..f8706f9e97 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Models.ContentEditing public string EmailHash { get; set; } [DataMember(Name = "lastLoginDate")] - public DateTime LastLoginDate { get; set; } + public DateTime? LastLoginDate { get; set; } /// /// Returns a list of different size avatars diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 339de9387e..bd940a9c57 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -94,6 +94,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.Avatars, opt => opt.MapFrom(user => user.GetCurrentUserAvatarUrls(applicationContext.Services.UserService, applicationContext.ApplicationCache.RuntimeCache))) .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) + .ForMember(detail => detail.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?)user.LastLoginDate)) .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups.Select(x => x.Alias).ToArray())) .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) From 7f27745d27af1412aa70f39df9aa1bbdbcd01bfc Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 May 2017 09:36:07 +0200 Subject: [PATCH 132/510] starting to get the invite email with token working --- src/Umbraco.Web/Editors/UsersController.cs | 16 +++++++++++++++- .../Models/Mapping/UserModelMapper.cs | 7 ++++--- .../WebApi/UmbracoAuthorizedApiController.cs | 8 ++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 9de3faefe8..6bfcd5bdbe 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -9,18 +9,22 @@ using System.Web; using System.Web.Http; using AutoMapper; using ClientDependency.Core; +using Microsoft.AspNet.Identity; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; +using IUser = Umbraco.Core.Models.Membership.IUser; namespace Umbraco.Web.Editors { @@ -47,6 +51,8 @@ namespace Umbraco.Web.Editors { } + + /// /// Returns a list of the sizes of gravatar urls for the user or null if the gravatar server cannot be reached /// @@ -229,7 +235,7 @@ namespace Umbraco.Web.Editors /// /// This will email the user an invite and generate a token that will be validated in the email /// - public UserDisplay PostInviteUser(UserInvite userSave) + public async Task PostInviteUser(UserInvite userSave) { if (userSave == null) throw new ArgumentNullException("userSave"); @@ -256,6 +262,14 @@ namespace Umbraco.Web.Editors Services.UserService.Save(user); + //TODO: Send an email! + await UserManager.EmailService.SendAsync(new IdentityMessage + { + Body = "You have been invited to the Umbraco Back Office!", + Destination = userSave., + Subject = "You have been invited to the Umbraco Back Office!" + }); + return Mapper.Map(user); } diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index bd940a9c57..cbcb7ab634 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -48,14 +48,15 @@ namespace Umbraco.Web.Models.Mapping }); config.CreateMap() - .ConstructUsing(invite => new User(invite.Name, invite.Email, invite.Email, Guid.NewGuid().ToString("N"))) + .ConstructUsing(invite => new User(invite.Name, invite.Email, invite.Email, Guid.NewGuid().ToString("N"))) + //generate a token for the invite + .ForMember(user => user.SecurityStamp, expression => expression.MapFrom(x => (DateTime.Now + x.Email).ToSHA1())) .ForMember(user => user.Id, expression => expression.Ignore()) .ForMember(user => user.Avatar, expression => expression.Ignore()) .ForMember(user => user.SessionTimeout, expression => expression.Ignore()) .ForMember(user => user.StartContentIds, expression => expression.Ignore()) .ForMember(user => user.StartMediaIds, expression => expression.Ignore()) - .ForMember(user => user.Language, expression => expression.Ignore()) - .ForMember(user => user.SecurityStamp, expression => expression.Ignore()) + .ForMember(user => user.Language, expression => expression.Ignore()) .ForMember(user => user.ProviderUserKey, expression => expression.Ignore()) .ForMember(user => user.Username, expression => expression.Ignore()) .ForMember(user => user.RawPasswordValue, expression => expression.Ignore()) diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index 9577e840e1..8328cfa393 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -5,6 +5,8 @@ using Umbraco.Core.Configuration; using Umbraco.Web.Security; using Umbraco.Web.WebApi.Filters; using umbraco.BusinessLogic; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; namespace Umbraco.Web.WebApi { @@ -34,6 +36,12 @@ namespace Umbraco.Web.WebApi { } + private BackOfficeUserManager _userManager; + protected BackOfficeUserManager UserManager + { + get { return _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager()); } + } + private bool _userisValidated = false; /// From 2087125d958f9b888614b72d630b857b4c683c8b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 May 2017 14:16:48 +0200 Subject: [PATCH 133/510] Fixes an issue with model mappings and group assignments --- .../Models/Identity/IdentityModelMappings.cs | 2 +- src/Umbraco.Core/Models/Rdbms/UserDto.cs | 4 +-- .../Models/Rdbms/UserStartNodeDto.cs | 35 +++++++++++++++++-- .../Persistence/Relators/UserGroupRelator.cs | 14 ++------ .../Security/BackOfficeUserStore.cs | 3 +- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index 9d01056f48..d48e2e6318 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Models.Identity .ForMember(user => user.StartMediaIds, expression => expression.MapFrom(user => user.StartMediaIds)) .ForMember(user => user.StartContentIds, expression => expression.MapFrom(user => user.StartContentIds)) .ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts)) - .ForMember(user => user.Groups, expression => expression.MapFrom(user => user.Groups.ToArray())) + .ForMember(user => user.Groups, expression => expression.MapFrom(user => user.Groups.Select(x => x.Alias).ToArray())) .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray())); config.CreateMap() diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Models/Rdbms/UserDto.cs index 98dee12a94..e804a949da 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserDto.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Models.Rdbms public UserDto() { UserGroupDtos = new List(); - UserStartNodeDtos = new List(); + UserStartNodeDtos = new HashSet(); } [Column("id")] @@ -91,6 +91,6 @@ namespace Umbraco.Core.Models.Rdbms public List UserGroupDtos { get; set; } [ResultColumn] - public List UserStartNodeDtos { get; set; } + public HashSet UserStartNodeDtos { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs b/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs index 42f3e5a7ca..fa8af80759 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using System; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms @@ -6,7 +7,7 @@ namespace Umbraco.Core.Models.Rdbms [TableName("umbracoUserStartNode")] [PrimaryKey("id", autoIncrement = true)] [ExplicitColumns] - internal class UserStartNodeDto + internal class UserStartNodeDto : IEquatable { [Column("id")] [PrimaryKeyColumn(Name = "PK_userStartNode")] @@ -32,5 +33,35 @@ namespace Umbraco.Core.Models.Rdbms Content = 1, Media = 2 } + + public bool Equals(UserStartNodeDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UserStartNodeDto) obj); + } + + public override int GetHashCode() + { + return Id; + } + + public static bool operator ==(UserStartNodeDto left, UserStartNodeDto right) + { + return Equals(left, right); + } + + public static bool operator !=(UserStartNodeDto left, UserStartNodeDto right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs index 435b02c2e3..fdea52498a 100644 --- a/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs +++ b/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Persistence.Relators // Setup the new current user _currentUser = user; _currentUser.UserGroupDtos = new List(); - _currentUser.UserStartNodeDtos = new List(); + _currentUser.UserStartNodeDtos = new HashSet(); AddOrUpdateGroup(group, section); AddOrUpdateStartNode(startNode); @@ -50,16 +50,8 @@ namespace Umbraco.Core.Persistence.Relators if (startNode == null || startNode.Id == default(int)) return; - //check if this is a new start node - var latestStartNode = _currentUser.UserStartNodeDtos.Count > 0 - ? _currentUser.UserStartNodeDtos[_currentUser.UserStartNodeDtos.Count - 1] - : null; - - if (latestStartNode == null || latestStartNode.Id != startNode.Id) - { - //add the current (new) start node - _currentUser.UserStartNodeDtos.Add(startNode); - } + //add the current (new) start node - this is a hashset so it will only allow unique rows so no need to check for existence + _currentUser.UserStartNodeDtos.Add(startNode); } private void AddOrUpdateGroup(UserGroupDto group, UserGroup2AppDto section) diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index d8bf69641d..3dcb74b62f 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -678,7 +678,7 @@ namespace Umbraco.Core.Security anythingChanged = true; user.StartMediaIds = identityUser.StartMediaIds; } - if (user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds)) + if (user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds) == false) { anythingChanged = true; user.StartContentIds = identityUser.StartContentIds; @@ -690,6 +690,7 @@ namespace Umbraco.Core.Security } var userGroups = user.Groups.Select(x => x.Alias).ToArray(); + if (userGroups.ContainsAll(identityUser.Groups) == false || identityUser.Groups.ContainsAll(userGroups) == false) { From 273c934e2639f1820a620557a51db882dd460e6e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 May 2017 15:13:04 +0200 Subject: [PATCH 134/510] wires up the invite user to the rest endpoint --- src/Umbraco.Core/Security/EmailService.cs | 4 +- .../src/common/resources/users.resource.js | 20 +++- .../src/common/services/util.service.js | 17 +++- .../users/views/users/users.controller.js | 99 ++++++++++++------- src/Umbraco.Web/Editors/UsersController.cs | 29 ++++-- 5 files changed, 121 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Core/Security/EmailService.cs b/src/Umbraco.Core/Security/EmailService.cs index 807d528f3d..a461f62926 100644 --- a/src/Umbraco.Core/Security/EmailService.cs +++ b/src/Umbraco.Core/Security/EmailService.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Security { public class EmailService : IIdentityMessageService { - public async Task SendAsync(IdentityMessage message) + public Task SendAsync(IdentityMessage message) { using (var client = new SmtpClient()) using (var mailMessage = new MailMessage()) @@ -19,7 +19,7 @@ namespace Umbraco.Core.Security mailMessage.IsBodyHtml = message.Body.IsNullOrWhiteSpace() == false && message.Body.Contains("<") && message.Body.Contains("(userSave); + + var link = string.Format("{0}#/login/false?invite={1}", + ApplicationContext.UmbracoApplicationUrl, + user.SecurityStamp.ToUrlBase64()); + + try + { + await UserManager.EmailService.SendAsync(new IdentityMessage + { + Body = string.Format("You have been invited to the Umbraco Back Office!\n\nClick this link to accept the invite\n\n{0}", link), + Destination = userSave.Email, + Subject = "You have been invited to the Umbraco Back Office!" + }); + } + catch (Exception ex) + { + throw new HttpResponseException( + Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex)); + } + + //Email was successful, so save the user now Services.UserService.Save(user); - //TODO: Send an email! - await UserManager.EmailService.SendAsync(new IdentityMessage - { - Body = "You have been invited to the Umbraco Back Office!", - Destination = userSave., - Subject = "You have been invited to the Umbraco Back Office!" - }); - return Mapper.Map(user); } From 6cd52bbc3c1574c1f3813efd4c660dcdb577c186 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 May 2017 15:55:36 +0200 Subject: [PATCH 135/510] Gets user invite generating the token, sending the email and then verifying the token --- .../Persistence/Mappers/UserMapper.cs | 1 + src/Umbraco.Core/Services/IUserService.cs | 9 ++- src/Umbraco.Core/Services/UserService.cs | 24 ++++++ .../src/common/resources/auth.resource.js | 74 ++++++++++++------- .../views/common/dialogs/login.controller.js | 54 ++++++++------ .../src/views/common/dialogs/login.html | 6 +- .../Editors/AuthenticationController.cs | 23 +++++- .../Editors/MediaTypeController.cs | 2 +- .../Models/Mapping/UserModelMapper.cs | 2 + 9 files changed, 137 insertions(+), 58 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs index 06e906143a..9d30f571b4 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs @@ -43,6 +43,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.LastLockoutDate, dto => dto.LastLockoutDate); CacheMap(src => src.LastLoginDate, dto => dto.LastLoginDate); CacheMap(src => src.LastPasswordChangeDate, dto => dto.LastPasswordChangeDate); + CacheMap(src => src.SecurityStamp, dto => dto.SecurityStampToken); } #endregion diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 6b798862b0..8aaa35f92e 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -9,7 +9,14 @@ namespace Umbraco.Core.Services /// Defines the UserService, which is an easy access to operations involving and eventually Users. /// public interface IUserService : IMembershipUserService - { + { + /// + /// Checks if a valid token is specified for an invited user and if so returns the user object + /// + /// + /// + IUser ValidateInviteToken(string token); + IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, UserState? userState = null, string[] userGroups = null, string filter = ""); diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index dd81824f6a..e542195a01 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -489,6 +489,30 @@ namespace Umbraco.Core.Services } } + public IUser ValidateInviteToken(string token) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateUserRepository(uow); + var query = new Query(); + + query.Where(member => member.SecurityStamp == token); + + var found = repository.GetByQuery(query).ToArray(); + + if (found.Length == 0) return null; + + var user = found[0]; + + //they must not be approved for an invite to work + if (user.IsApproved) return null; + //they should have never logged in for an invite to work + if (user.LastLoginDate != default(DateTime)) return null; + + return user; + } + } + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, UserState? userState = null, string[] userGroups = null, string filter = "") { using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index 40f1dbb807..d1e9c59b80 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -14,7 +14,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { return { get2FAProviders: function () { - + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( @@ -69,7 +69,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { * */ performLogin: function (username, password) { - + if (!username || !password) { return angularHelper.rejectedPromise({ errorMsg: 'Username or password cannot be empty' @@ -81,12 +81,30 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { umbRequestHelper.getApiUrl( "authenticationApiBaseUrl", "PostLogin"), { - username: username, - password: password - }), + username: username, + password: password + }), 'Login failed for user ' + username); }, + verifyInvite: function (token) { + + if (!token) { + return angularHelper.rejectedPromise({ + errorMsg: 'Token cannot be empty' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostVerifyInvite", { + token: token + })), + 'Failed to verify token ' + token); + }, + /** * @ngdoc method * @name umbraco.resources.authResource#performRequestPasswordReset @@ -114,7 +132,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { errorMsg: 'Email address cannot be empty' }); } - + //TODO: This validation shouldn't really be done here, the validation on the login dialog // is pretty hacky which is why this is here, ideally validation on the login dialog would // be done properly. @@ -130,11 +148,11 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { umbRequestHelper.getApiUrl( "authenticationApiBaseUrl", "PostRequestPasswordReset"), { - email: email - }), + email: email + }), 'Request password reset failed for email ' + email); }, - + /** * @ngdoc method * @name umbraco.resources.authResource#performValidatePasswordResetCode @@ -173,11 +191,11 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { $http.post( umbRequestHelper.getApiUrl( "authenticationApiBaseUrl", - "PostValidatePasswordResetCode"), - { - userId: userId, - resetCode: resetCode - }), + "PostValidatePasswordResetCode"), + { + userId: userId, + resetCode: resetCode + }), 'Password reset code validation failed for userId ' + userId + ', code' + resetCode); }, @@ -234,11 +252,11 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { umbRequestHelper.getApiUrl( "authenticationApiBaseUrl", "PostSetPassword"), - { - userId: userId, - password: password, - resetCode: resetCode - }), + { + userId: userId, + password: password, + resetCode: resetCode + }), 'Password reset code validation failed for userId ' + userId); }, @@ -254,9 +272,9 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { umbRequestHelper.getApiUrl( "authenticationApiBaseUrl", "PostUnLinkLogin"), { - loginProvider: loginProvider, - providerKey: providerKey - }), + loginProvider: loginProvider, + providerKey: providerKey + }), 'Unlinking login provider failed'); }, @@ -278,14 +296,14 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { * @returns {Promise} resourcePromise object * */ - performLogout: function() { + performLogout: function () { return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "authenticationApiBaseUrl", "PostLogout"))); }, - + /** * @ngdoc method * @name umbraco.resources.authResource#getCurrentUser @@ -305,13 +323,13 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { * */ getCurrentUser: function () { - + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "authenticationApiBaseUrl", "GetCurrentUser")), - 'Server call failed for getting current user'); + 'Server call failed for getting current user'); }, getCurrentUserLinkedLogins: function () { @@ -323,7 +341,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { "GetCurrentUserLinkedLogins")), 'Server call failed for getting current users linked logins'); }, - + /** * @ngdoc method * @name umbraco.resources.authResource#isAuthenticated @@ -357,7 +375,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { } return data; }, - error: function (data, status, headers, config) { + error: function (data, status, headers, config) { return { errorMsg: 'Server call failed for checking authentication', data: data, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 106e748c20..c925598bcd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,23 +1,29 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", function ($scope, $cookies, $location, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService) { - $scope.isInvite = false; + $scope.invitedUser = null; function init() { // Check if it is a new user if ($location.search().invite) { - $scope.isInvite = true; - $scope.inviteSetPassword = true; + var token = $location.search().invite; + authResource.verifyInvite(token).then(function (data) { + $scope.invitedUser = data; + $scope.inviteSetPassword = true; + }, function () { + //it failed so we should remove the search + $location.search('invite', null); + }); } } - $scope.inviteSavePassword = function() { + $scope.inviteSavePassword = function () { $scope.inviteSetPassword = false; $scope.inviteSetAvatar = true; }; - var setFieldFocus = function(form, field) { - $timeout(function() { + var setFieldFocus = function (form, field) { + $timeout(function () { $("form[name='" + form + "'] input[name='" + field + "']").focus(); }); } @@ -112,7 +118,7 @@ } $scope.loginSubmit = function (login, password) { - + //if the login and password are not empty we need to automatically // validate them - this is because if there are validation errors on the server // then the user has to change both username & password to resubmit which isn't ideal, @@ -127,24 +133,24 @@ } userService.authenticate(login, password) - .then(function(data) { - $scope.submit(true); - }, - function(reason) { + .then(function (data) { + $scope.submit(true); + }, + function (reason) { - //is Two Factor required? - if (reason.status === 402) { - $scope.errorMsg = "Additional authentication required"; - show2FALoginDialog(reason.data.twoFactorView, $scope.submit); - } - else { - $scope.errorMsg = reason.errorMsg; + //is Two Factor required? + if (reason.status === 402) { + $scope.errorMsg = "Additional authentication required"; + show2FALoginDialog(reason.data.twoFactorView, $scope.submit); + } + else { + $scope.errorMsg = reason.errorMsg; - //set the form inputs to invalid - $scope.loginForm.username.$setValidity("auth", false); - $scope.loginForm.password.$setValidity("auth", false); - } - }); + //set the form inputs to invalid + $scope.loginForm.username.$setValidity("auth", false); + $scope.loginForm.password.$setValidity("auth", false); + } + }); //setup a watch for both of the model values changing, if they change // while the form is invalid, then revalidate them so that the form can @@ -166,7 +172,7 @@ if (email && email.length > 0) { $scope.requestPasswordResetForm.email.$setValidity('auth', true); } - + $scope.showEmailResetConfirmation = false; if ($scope.requestPasswordResetForm.$invalid) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 991b52cc62..49b4cadc0d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -8,10 +8,10 @@ -