From 45f574ed3a6c90137d1e0a18b8a3188b30685339 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 29 Sep 2018 07:27:39 +0200 Subject: [PATCH 001/585] Ensure the correct color for items in the Nested Content item picker --- .../less/components/umb-nested-content.less | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 514a73407c..e433b9fe4a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -208,11 +208,25 @@ width: 99%; } -.usky-grid.umb-nested-content__node-type-picker .cell-tools-menu { - position: relative; - transform: translate(-50%, -25%); -} +.usky-grid.umb-nested-content__node-type-picker { + .cell-tools-menu { + position: relative; + transform: translate(-50%, -25%); + } + .elements li { + &:hover { + i { + color: @white !important; + } + } + + i { + // make sure the item icons shown are in the correct color according to their doc type icon instead of the grid editor item color + color: unset; + } + } +} // this resolves the layout issue introduced in nested content in 7.12 with the addition of the input for link anchors // the attribute selector ensures the change only applies to the linkpicker overlay From c6e2ced971e3fefba29c0082be9c29431fe98984 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 29 Sep 2018 07:30:27 +0200 Subject: [PATCH 002/585] Fix Nested Content "second time dragging" issue --- .../src/less/components/umb-nested-content.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 514a73407c..31ebe15d8a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -1,5 +1,6 @@ .umb-nested-content { text-align: center; + position: relative; } .umb-nested-content--not-supported { From a7573ce31f1f166f86c821ddbbb169383c904281 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 29 Sep 2018 07:35:16 +0200 Subject: [PATCH 003/585] Avoid data loss when dragging an unsaved Nested Content item (#3014) --- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 0a44a9fcaa..c527cc50a0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -231,6 +231,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop cursor: "move", handle: ".umb-nested-content__icon--move", start: function (ev, ui) { + updateModel(); // Yea, yea, we shouldn't modify the dom, sue me $("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () { tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id')); From 0dbbb585ad2c76647ae151403e3cb3d81c8ac254 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 29 Sep 2018 17:06:04 +0200 Subject: [PATCH 004/585] Fix loading issue for RTE's in Nested Content --- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 7 +++++++ .../src/views/propertyeditors/rte/rte.html | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index fabe7c2291..4805c820ee 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -115,6 +115,13 @@ div.umb-codeeditor .umb-btn-toolbar { // // RTE // -------------------------------------------------- +.umb-rte { + position: relative; + + .-loading { + position: absolute; + } +} .mce-tinymce{border: 1px solid @gray-8 !important; border-radius: 0px !important;} .mce-panel{background: @gray-10 !important; border-color: @gray-8 !important;} .mce-btn-group, .mce-btn{border: none !important; background: none !important;} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html index 774f860c31..b9c2364925 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html @@ -1,5 +1,5 @@
-
Loading...
+
Loading...
From 2c9256c6724d2e840b0b267657a3db930f00e225 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 30 Sep 2018 18:50:33 +0200 Subject: [PATCH 005/585] Don't let the same doctype be selectable twice in the Nested Content configuration --- .../nestedcontent/nestedcontent.controller.js | 12 ++++++++++++ .../nestedcontent/nestedcontent.doctypepicker.html | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 0a44a9fcaa..7be141362e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -37,6 +37,18 @@ }); }); + $scope.selectableDocTypesFor = function (config) { + // return all doctypes that are: + // 1. either already selected for this config, or + // 2. not selected in any other config + return _.filter($scope.model.docTypes, function (docType) { + return docType.alias === config.ncAlias || !_.find($scope.model.value, function(c) { + return docType.alias === c.ncAlias; + }); + }); + + } + if (!$scope.model.value) { $scope.model.value = []; $scope.add(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html index 0617331682..e0a16d8687 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html @@ -22,7 +22,7 @@ From eda6e084a78f88afd53802bf42cad658f71e71e3 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 1 Oct 2018 14:37:40 +0200 Subject: [PATCH 006/585] Fix casing issue in member group service/repo --- .../Repositories/MemberGroupRepository.cs | 2 +- .../Services/MemberServiceTests.cs | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 404c32640a..8b5dbb435e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -284,7 +284,7 @@ namespace Umbraco.Core.Persistence.Repositories var nonAssignedRoles = roleNames.Except(assignedRoles, StringComparer.CurrentCultureIgnoreCase); foreach (var toAssign in nonAssignedRoles) { - var groupId = rolesForNames.First(x => x.Text == toAssign).NodeId; + var groupId = rolesForNames.First(x => x.Text.InvariantEquals(toAssign)).NodeId; Database.Insert(new Member2MemberGroupDto { Member = mId, MemberGroup = groupId }); } } diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 4ba3866c64..3ee5a4804e 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -288,6 +288,29 @@ namespace Umbraco.Tests.Services Assert.AreEqual(2, membersInRole.Count()); } + [Test] + public void Associate_Members_To_Roles_With_Member_Id_Casing() + { + ServiceContext.MemberService.AddRole("MyTestRole1"); + + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var member1 = MockedMember.CreateSimpleMember(memberType, "test1", "test1@test.com", "pass", "test1"); + ServiceContext.MemberService.Save(member1); + var member2 = MockedMember.CreateSimpleMember(memberType, "test2", "test2@test.com", "pass", "test2"); + ServiceContext.MemberService.Save(member2); + + // temp make sure they exist + Assert.IsNotNull(ServiceContext.MemberService.GetById(member1.Id)); + Assert.IsNotNull(ServiceContext.MemberService.GetById(member2.Id)); + + ServiceContext.MemberService.AssignRoles(new[] { member1.Id, member2.Id }, new[] { "mytestrole1" }); + + var membersInRole = ServiceContext.MemberService.GetMembersInRole("MyTestRole1"); + + Assert.AreEqual(2, membersInRole.Count()); + } + [Test] public void Associate_Members_To_Roles_With_Member_Username() { @@ -1179,4 +1202,4 @@ namespace Umbraco.Tests.Services } } -} \ No newline at end of file +} From 66221364ca7fd489b28ab44135c8c9174cc072cf Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Mon, 1 Oct 2018 21:21:17 +0200 Subject: [PATCH 007/585] Hide "Created Date" field when the media hasn't yet been created --- .../src/views/components/media/umb-media-node-info.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html index 19095acb90..7cfcb835a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html @@ -29,11 +29,11 @@ - + {{node.createDateFormatted}} by {{ node.owner.name }} - + {{node.updateDateFormatted}} @@ -47,7 +47,7 @@ - +
{{ node.id }}
{{ node.key }}
From 9c04abc5ad7eef49ac7b3d829083832f3284667b Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Mon, 1 Oct 2018 21:40:24 +0200 Subject: [PATCH 008/585] Hide the "Links" box when there are no links --- .../src/views/components/content/umb-content-node-info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 0706e9596c..7085babf59 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -2,7 +2,7 @@
- +
From 0a245dda4358206df1d4da3c79541201eb21b888 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 2 Oct 2018 19:54:12 +0200 Subject: [PATCH 019/585] Add an outer check to make sure the scope.includeSubFolders property is not undefined, which makes the media render in the view again --- .../components/umbmediagrid.directive.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 9aa4965022..d31ef61150 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -125,13 +125,19 @@ Use this directive to generate a thumbnail grid of media items. i--; } - if (scope.includeSubFolders !== 'true') { - if (item.parentId !== parseInt(scope.currentFolderId)) { - scope.items.splice(i, 1); - i--; + + // If subfolder search is not enabled remove the media items that's not needed + // Make sure that includeSubFolder is not undefined since the directive is used + // in contexts where it should not be used. Currently only used when we trigger + // a media picker + if(scope.includeSubFolders !== undefined){ + if (scope.includeSubFolders !== 'true') { + if (item.parentId !== parseInt(scope.currentFolderId)) { + scope.items.splice(i, 1); + i--; + } } } - } @@ -152,7 +158,7 @@ Use this directive to generate a thumbnail grid of media items. } if (!item.isFolder) { - + // handle entity if(item.image) { item.thumbnail = mediaHelper.resolveFileFromEntity(item, true); @@ -161,7 +167,7 @@ Use this directive to generate a thumbnail grid of media items. } else { item.thumbnail = mediaHelper.resolveFile(item, true); item.image = mediaHelper.resolveFile(item, false); - + var fileProp = _.find(item.properties, function (v) { return (v.alias === "umbracoFile"); }); From a8df8f484ff7eb8e03584f3691bf9b9001ba9849 Mon Sep 17 00:00:00 2001 From: KimHolzmann Date: Wed, 3 Oct 2018 18:13:35 +0200 Subject: [PATCH 020/585] #3022 media uploader in rte doesn't select uploaded image (#3117) --- .../common/overlays/mediaPicker/mediapicker.controller.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index 60c89197a9..7c43f5909f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -237,7 +237,10 @@ angular.module("umbraco") $scope.onUploadComplete = function(files) { $scope.gotoFolder($scope.currentFolder).then(function() { if (files.length === 1 && $scope.model.selectedImages.length === 0) { - selectImage($scope.images[$scope.images.length - 1]); + var image = $scope.images[$scope.images.length - 1]; + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + selectImage(image); } }); }; From c27b9d76dbb0c72a700a6af13f4b50c50994a4b8 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 3 Oct 2018 08:09:39 +0200 Subject: [PATCH 021/585] fixes #3126 - If no property data is found for a specific version - a null exception is thrown. - Throwing an exception with a description instead of hitting a null exception. --- .../Repositories/ContentRepository.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 6c00964c15..4f19dc9241 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -134,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories .InnerJoin(SqlSyntax) .On(SqlSyntax, left => left.NodeId, right => right.NodeId) .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) .InnerJoin() .On(left => left.NodeId, right => right.ContentTypeId); //TODO: IF we want to enable querying on content type information this will need to be joined @@ -836,19 +836,19 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", } public int CountPublished(string contentTypeAlias = null) - { - if (contentTypeAlias.IsNullOrWhiteSpace()) - { + { + if (contentTypeAlias.IsNullOrWhiteSpace()) + { var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true); - return Database.ExecuteScalar(sql); + .Where(x => x.Published == true); + return Database.ExecuteScalar(sql); } - else - { + else + { var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true) - .Where(x => x.Alias == contentTypeAlias); - return Database.ExecuteScalar(sql); + .Where(x => x.Published == true) + .Where(x => x.Alias == contentTypeAlias); + return Database.ExecuteScalar(sql); } } @@ -1220,7 +1220,14 @@ ORDER BY cmsContentVersion.id DESC if (def.DocumentDto.TemplateId.HasValue) templates.TryGetValue(def.DocumentDto.TemplateId.Value, out template); // else null cc.Template = template; - cc.Properties = propertyData[cc.Version]; + if (propertyData.ContainsKey(cc.Version)) + { + cc.Properties = propertyData[cc.Version]; + } + else + { + throw new InvalidOperationException($"No property data found for version: '{cc.Version}'."); + } //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -1307,4 +1314,4 @@ ORDER BY cmsContentVersion.id DESC } } } -} \ No newline at end of file +} From 9579b97dde0aca64f5aeaed76d291661bf54141c Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 3 Oct 2018 08:01:26 +0200 Subject: [PATCH 022/585] fixes #3124 - logging in BaseDataCreation. --- .../Persistence/DatabaseSchemaHelper.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs index f03e751427..304cdc185f 100644 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs @@ -117,6 +117,8 @@ namespace Umbraco.Core.Persistence var tableExist = TableExist(tableName); if (overwrite && tableExist) { + _logger.Info(string.Format("Table '{0}' already exists, but will be recreated", tableName)); + DropTable(tableName); tableExist = false; } @@ -169,13 +171,22 @@ namespace Umbraco.Core.Persistence _logger.Info(string.Format("Create Foreign Key sql {0}:\n {1}", createdFk, sql)); } - - transaction.Complete(); + if (overwrite) + { + _logger.Info(string.Format("Table '{0}' was recreated", tableName)); + } + else + { + _logger.Info(string.Format("New table '{0}' was created", tableName)); + } } } - - _logger.Info(string.Format("New table '{0}' was created", tableName)); + else + { + // The table exists and was not recreated/overwritten. + _logger.Info(string.Format("Table '{0}' already exists - no changes were made", tableName)); + } } public void DropTable() From bc081db9e6af1cb41b777755a4b9b8b93b0fa963 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Thu, 4 Oct 2018 08:16:11 +0200 Subject: [PATCH 023/585] Sentences should end with a period (#3144) --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 ++-- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 8984ffa81a..71195be6f8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -259,7 +259,7 @@ Slip filerne her... Link til medie eller klik her for at vælge filer - Du kan trække filer herind for at uploade + Du kan trække filer herind for at uploade. Tilladte filtyper er kun Kan ikke uploade denne fil, den har ikke en godkendt filtype Maks filstørrelse er diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 057eb7dc9f..f12d509416 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -267,7 +267,7 @@ Drop your files here... Link to media or click here to choose files - You can drag files here to upload + You can drag files here to upload. Only allowed file types are Cannot upload this file, it does not have an approved file type Max file size is @@ -647,7 +647,7 @@ Permissions Scheduled Publishing Search - Sorry, we can not find what you are looking for + Sorry, we can not find what you are looking for. No items have been added Server Show 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 8a24c4a7c2..c6a6aadd77 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -268,7 +268,7 @@ Drop your files here... Link to media or click here to choose files - You can drag files here to upload + You can drag files here to upload. Only allowed file types are Cannot upload this file, it does not have an approved file type Max file size is @@ -647,7 +647,7 @@ Permissions Scheduled Publishing Search - Sorry, we can not find what you are looking for + Sorry, we can not find what you are looking for. No items have been added Server Show From deca5d7bdb056363175e6dd06e3415304a12d44b Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Wed, 3 Oct 2018 22:10:32 +0200 Subject: [PATCH 024/585] Refactor error handling to serve different error messages depending on the context. If we know that the id being moved is the same as the id we want to move it to then show a specific error message hinting the editor what's wrong and otherwise show the usual generic error message. --- .../src/common/resources/media.resource.js | 108 ++++++++++-------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 93a92db1ec..ca106cf65c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -46,7 +46,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * $scope.complete = true; * }); - * + * * @param {Object} args arguments object * @param {Int} args.parentId the ID of the parent node * @param {Array} options.sortedIds array of node IDs as they should be sorted @@ -87,9 +87,9 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was moved"); * }, function(err){ - * alert("node didnt move:" + err.data.Message); + * alert("node didnt move:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.idd the ID of the node to move * @param {Int} args.parentId the ID of the parent node to move to @@ -109,11 +109,23 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move media'); + { + parentId: args.parentId, + id: args.id + }), + { + error: function(data){ + var errorMsg = 'Failed to move media'; + + if(data.parentId === data.id){ + errorMsg = 'Media can\'t be moved into itself'; + } + + return { + errorMsg: errorMsg + }; + } + }); }, @@ -129,12 +141,12 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * mediaResource.getById(1234)
           *    .then(function(media) {
-          *        var myMedia = media; 
+          *        var myMedia = media;
           *        alert('its here!');
           *    });
-          * 
- * - * @param {Int} id id of media item to return + * + * + * @param {Int} id id of media item to return * @returns {Promise} resourcePromise object containing the media item. * */ @@ -163,9 +175,9 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its gone!'); * }); - * - * - * @param {Int} id id of media item to delete + * + * + * @param {Int} id id of media item to delete * @returns {Promise} resourcePromise object. * */ @@ -191,12 +203,12 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * mediaResource.getByIds( [1234,2526,28262])
           *    .then(function(mediaArray) {
-          *        var myDoc = contentArray; 
+          *        var myDoc = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * - * @param {Array} ids ids of media items to return as an array + * + * + * @param {Array} ids ids of media items to return as an array * @returns {Promise} resourcePromise object containing the media items array. * */ @@ -223,28 +235,28 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * + * * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * * The scaffold is used to build editors for media that has not yet been populated with data. - * + * * ##usage *
           * mediaResource.getScaffold(1234, 'folder')
           *    .then(function(scaffold) {
           *        var myDoc = scaffold;
-          *        myDoc.name = "My new media item"; 
+          *        myDoc.name = "My new media item";
           *
           *        mediaResource.save(myDoc, true)
           *            .then(function(media){
           *                alert("Retrieved, updated and saved again");
           *            });
           *    });
-          * 
- * + * + * * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on + * @param {String} alias mediatype alias to base the scaffold on * @returns {Promise} resourcePromise object containing the media scaffold. * */ @@ -283,11 +295,11 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
           *    .then(function(contentArray) {
-          *        var children = contentArray; 
+          *        var children = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * + * + * * @param {Int} parentid id of content item to return children of * @param {Object} options optional options object * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 @@ -361,9 +373,9 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
           * mediaResource.getById(1234)
@@ -374,11 +386,11 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                alert("Retrieved, updated and saved again");
           *            });
           *    });
-          * 
- * + * + * * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item * @returns {Promise} resourcePromise object containing the saved media item. * */ @@ -400,10 +412,10 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function(folder) { * alert('New folder'); * }); - * + * * * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath + * @param {int} parentId Id of the media item to create the folder underneath * @returns {Promise} resourcePromise object. * */ @@ -427,18 +439,18 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * Retrieves all media children with types used as folders. * Uses the convention of looking for media items with mediaTypes ending in * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * + * * NOTE: This will return a max of 500 folders, if more is required it needs to be paged - * + * * ##usage *
           * mediaResource.getChildFolders(1234)
           *    .then(function(data) {
           *        alert('folders');
           *    });
-          * 
+ * * - * @param {int} parentId Id of the media item to query for child folders + * @param {int} parentId Id of the media item to query for child folders * @returns {Promise} resourcePromise object. * */ @@ -473,8 +485,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its empty!'); * }); - * - * + * + * * @returns {Promise} resourcePromise object. * */ @@ -501,8 +513,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function(searchResult) { * alert('it's here!'); * }); - * - * + * + * * @param {string} query The search query * @param {int} pageNumber The page number * @param {int} pageSize The number of media items on a page @@ -513,7 +525,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { search: function (query, pageNumber, pageSize, searchFrom) { var args = [ - { "query": query }, + { "query": query }, { "pageNumber": pageNumber }, { "pageSize": pageSize }, { "searchFrom": searchFrom } From 7355ba4b235a2ec9ad3cd61262a56673e5cf9ac3 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 4 Oct 2018 08:36:21 +0200 Subject: [PATCH 025/585] Add option to rename colorpicker labels (#3121) --- .../src/less/property-editors.less | 19 +++++++++++-------- .../colorpicker/colorpicker.prevalues.html | 12 ++++++++---- .../multicolorpicker.controller.js | 6 ++++-- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 1c647eb1d0..d14a1abae9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -144,11 +144,14 @@ div.umb-codeeditor .umb-btn-toolbar { /* pre-value editor */ .control-group.color-picker-preval { .thumbnail { - width: 36px; + width: 34px; + height: 34px; min-width: auto; border: none; cursor: move; border-radius: 3px; + margin-top: auto; + margin-bottom: auto; } .handle { @@ -160,19 +163,19 @@ div.umb-codeeditor .umb-btn-toolbar { div.color-picker-prediv { display: inline-flex; align-items: center; - max-width: 85%; + max-width: 100%; + flex: 1; pre { display: inline-flex; font-family: monospace; - margin-right: 10px; - margin-left: 10px; + margin-left: 15px; + margin-right: 15px; white-space: nowrap; overflow: hidden; margin-bottom: 0; vertical-align: middle; - padding-top: 7px; - padding-bottom: 7px; + padding: 6px 10px; background: #f7f7f7; flex: 0 0 auto; } @@ -201,11 +204,11 @@ div.umb-codeeditor .umb-btn-toolbar { label { border: 1px solid #fff; - padding: 7px 10px; + padding: 6px 10px; font-family: monospace; border: 1px solid #dfdfe1; background: #f7f7f7; - margin: 0 15px 0 0; + margin: 0 15px 0 3px; border-radius: 3px; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html index 3d9e2efdf8..262c8593ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html @@ -3,18 +3,22 @@
- +
-
+
-
-
#{{item.value}}
{{item.label}}
+
+
+
#{{item.value}}
+ + +
Remove diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js index 7ae728a85a..287a0f48fc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js @@ -5,8 +5,9 @@ var defaultLabel = null; $scope.newColor = defaultColor; - $scope.newLavel = defaultLabel; + $scope.newLabel = defaultLabel; $scope.hasError = false; + $scope.focusOnNew = false; $scope.labels = {}; @@ -104,7 +105,6 @@ }; $scope.add = function (evt) { - evt.preventDefault(); if ($scope.newColor) { @@ -117,7 +117,9 @@ value: $scope.newColor, label: newLabel }); + $scope.newLabel = ""; $scope.hasError = false; + $scope.focusOnNew = true; return; } From ff7356fea29825d0bf93ba0d18ccfe1fdf36ad3b Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Oct 2018 13:06:07 +0200 Subject: [PATCH 026/585] Extending the AndSelect to take an param array of fields --- src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index d97c748b6f..46d44d6851 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -660,6 +660,18 @@ namespace Umbraco.Core.Persistence return sql.Select(sql.GetColumns(tableAlias: tableAlias, columnExpressions: fields)); } + /// + /// Adds columns to a SELECT Sql statement. + /// + /// The origin sql. + /// Expression indicating the column to select. + /// The Sql statement. + public static Sql AndSelect(this Sql sql, params string[] fields) + { + if (sql == null) throw new ArgumentNullException(nameof(sql)); + return sql.Append(", " + string.Join(", ", fields)); + } + /// /// Adds columns to a SELECT Sql statement. /// From f33953e702d4bad719f0bac880a3b7e6aa7797a9 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Oct 2018 13:12:49 +0200 Subject: [PATCH 027/585] Moving the sql join and coalesce query in the DocumentRepository.GetBaseQuery(), renaming "ordering" to "variantName" to match the field created in GetBaseQuery. --- .../Implement/ContentRepositoryBase.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 6ace73fbc3..974d3668fb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; +using Content = System.Web.UI.WebControls.Content; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -265,6 +266,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // create prepared sql // ensure it's single-line as NPoco PagingHelper has issues with multi-lines psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments); + + + // replace the magic culture parameter (see DocumentRepository.GetBaseQuery()) + if (!ordering.Culture.IsNullOrWhiteSpace()) + { + for (var i = 0; i < psql.Arguments.Length; i++) + { + if (psql.Arguments[i] is string s && s == "[[[ISOCODE]]]") + { + psql.Arguments[i] = ordering.Culture; + break; + } + } + } return psql; } @@ -342,20 +357,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ordering.Culture.IsNullOrWhiteSpace()) return GetAliasedField(SqlSyntax.GetFieldName(x => x.Text), sql); - // culture = must work on variant name ?? invariant name - // insert proper join and return coalesced ordering field - - var joins = Sql() - .LeftJoin(nested => - nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") - .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); - - // see notes in ApplyOrdering: the field MUST be selected + aliased - sql = Sql(InsertBefore(sql, "FROM", ", " + SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS ordering "), sql.Arguments); - - sql = InsertJoins(sql, joins); - - return "ordering"; + return "variantName"; } // previously, we'd accept anything and just sanitize it - not anymore @@ -435,6 +437,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // sort and filter sql = PreparePageSql(sql, filter, ordering); + // get a page of DTOs and the total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sql); totalRecords = Convert.ToInt32(pagedResult.TotalItems); From 76bacb3ba08f180cbb398dac7a3a61ce11da6d8d Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Oct 2018 13:15:01 +0200 Subject: [PATCH 028/585] Moved the join and coalesce into GetBaseQuery, making sure that we append the right query if we are searching a variant list --- .../Implement/DocumentRepository.cs | 59 ++++++++++++++----- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index fb1a33eb62..1c3edaca40 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using ColumnInfo = NPoco.ColumnInfo; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -110,12 +111,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Single: case QueryType.Many: sql = sql.Select(r => - r.Select(documentDto => documentDto.ContentDto, r1 => - r1.Select(contentDto => contentDto.NodeDto)) - .Select(documentDto => documentDto.DocumentVersionDto, r1 => - r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto)) - .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => - r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))); + r.Select(documentDto => documentDto.ContentDto, r1 => + r1.Select(contentDto => contentDto.NodeDto)) + .Select(documentDto => documentDto.DocumentVersionDto, r1 => + r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto)) + .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => + r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))) + .AndSelect(SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS variantName"); break; } @@ -125,17 +127,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin().On(left => left.NodeId, right => right.NodeId) // inner join on mandatory edited version - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.Id == right.Id) + .InnerJoin() + .On((left, right) => left.NodeId == right.NodeId) + .InnerJoin() + .On((left, right) => left.Id == right.Id) // left join on optional published version .LeftJoin(nested => - nested.InnerJoin("pdv").On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") + nested.InnerJoin("pdv") + .On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv"); + //the magic [[[ISOCODE]]] will be replaced in ContentRepositoryBase.GetPage() by the current Iso code + sql + .LeftJoin(nested => + nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") + .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); + sql .Where(x => x.NodeObjectType == NodeObjectTypeId); + // this would ensure we don't get the published version - keep for reference //sql // .WhereAny( @@ -146,6 +158,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (current) sql.Where(x => x.Current); // always get the current version + return sql; } @@ -235,7 +248,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content) entity; + var content = (Content)entity; content.AddingEntity(); var publishing = content.PublishedState == PublishedState.Publishing; @@ -405,7 +418,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content) entity; + var content = (Content)entity; // check if we need to make any database changes at all if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty()) @@ -414,7 +427,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // whatever we do, we must check that we are saving the current version // fixme maybe we can just fetch Current (bool) var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == content.VersionId)).FirstOrDefault(); - if (version == null || !version.Current ) + if (version == null || !version.Current) throw new InvalidOperationException("Cannot save a non-current version."); // update @@ -683,16 +696,30 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// public override IEnumerable GetPage(IQuery query, - long pageIndex, int pageSize, out long totalRecords, + long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering) { Sql filterSql = null; + var temp = Query().Where(x => x.Name.Contains("foo")); + var clause = temp.GetWhereClauses().First().Item1.Split(' ')[0]; + if (filter != null) { filterSql = Sql(); foreach (var filterClause in filter.GetWhereClauses()) - filterSql.Append($"AND ({filterClause.Item1})", filterClause.Item2); + { + // fixme - is this the right way of doing it??? + var where = filterClause.Item1.Split(' ')[0] == clause + // normally, this would be the field alias of the coalesce result between ContentVersionCulture and NodeDto names, however + // you can't refer to field alias in a WHERE clause so we have to put the coalesce calculation instead which refers to the original field + ? SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + : filterClause.Item1; + + filterSql.Append( + where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})", + filterClause.Item2); + } } return GetPage(query, pageIndex, pageSize, out totalRecords, @@ -892,7 +919,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var cached = IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id) { - content[i] = (Content) cached; + content[i] = (Content)cached; continue; } } @@ -1221,7 +1248,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // of whether the name has changed (ie the culture has been updated) - some saving culture // fr-FR could cause culture en-UK name to change - not sure that is clean - foreach(var (culture, name) in content.CultureNames) + foreach (var (culture, name) in content.CultureNames) { var langId = LanguageRepository.GetIdByIsoCode(culture); if (!langId.HasValue) continue; From 24fe3b33f2e65ab45aa8029a8eb0c98ecb3b8fc1 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Oct 2018 13:20:15 +0200 Subject: [PATCH 029/585] Cleaning some usings --- .../Repositories/Implement/ContentRepositoryBase.cs | 4 ---- .../Persistence/Repositories/Implement/DocumentRepository.cs | 1 - 2 files changed, 5 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 974d3668fb..b87190ae58 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -10,7 +9,6 @@ using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; @@ -19,9 +17,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; -using Content = System.Web.UI.WebControls.Content; namespace Umbraco.Core.Persistence.Repositories.Implement { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 1c3edaca40..6fc3653db3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -14,7 +14,6 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; using Umbraco.Core.Services; -using ColumnInfo = NPoco.ColumnInfo; namespace Umbraco.Core.Persistence.Repositories.Implement { From 9d36ee6bd8a87e49704b23995c34e81384a7414e Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Thu, 4 Oct 2018 15:49:32 +0200 Subject: [PATCH 030/585] #3141 fixed broken Do something else button --- .../src/common/services/navigation.service.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index da2fd243e6..8fb7137e9a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -73,7 +73,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("showSearchResults", false); appState.setGlobalState("stickyNavigation", false); appState.setGlobalState("showTray", false); - appState.setMenuState("currentNode", null); if (appState.getGlobalState("isTablet") === true) { appState.setGlobalState("showNavigation", false); From b6c32978848e7c3ef3f43d1689ea03a390e19c96 Mon Sep 17 00:00:00 2001 From: Jannik Anker Date: Thu, 4 Oct 2018 17:16:57 +0200 Subject: [PATCH 031/585] Lecoati CSS reference removed (issue #3147) ApprovedColorPickerController had a reference for no apparent reason. --- .../src/views/common/dialogs/approvedcolorpicker.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js index 5e5363ae6c..7d9a0d3410 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js @@ -19,7 +19,6 @@ angular.module("umbraco") $scope.classes.splice(0, 0, "noclass"); }) - assetsService.loadCss("/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css", $scope); assetsService.loadCss(cssPath, $scope); }); }); From e7d7de627307f9e0d3b5e00908c017f42057cee4 Mon Sep 17 00:00:00 2001 From: Rahul Arulkumaran Date: Fri, 5 Oct 2018 11:43:41 +0530 Subject: [PATCH 032/585] Update LICENSE.md --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index c5560c3ce1..fa83dba963 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # The MIT License (MIT) # -Copyright (c) 2013 Umbraco +Copyright (c) 2013-present Umbraco Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From 4ff15fbf047fe1ad43329de53c2fba9489a1483e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 5 Oct 2018 13:09:30 +0100 Subject: [PATCH 033/585] Updates constants to include nested class in Constants.Trees.Groups for the various settings section subtree groupings - these keys will be used to help group the trees --- src/Umbraco.Core/Constants-Applications.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 9a1883a065..1bb7585aef 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -145,6 +145,15 @@ public const string PartialViewMacros = "partialViewMacros"; + public static class Groups + { + public const string Settings = "settingsGroup"; + + public const string Templating = "templatingGroup"; + + public const string ThirdParty = "thirdPartyGroup"; + } + //TODO: Fill in the rest! } } From e013545cfc324b6733cd0be8e34ec3eb3eda5ffa Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 5 Oct 2018 13:10:31 +0100 Subject: [PATCH 034/585] Adds property to [CoreTree] attribute that allows you to set the TreeGroup key - which will be from our constants --- src/Umbraco.Web/Trees/CoreTreeAttribute.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs index 160f2a36d8..1b485aea6a 100644 --- a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs +++ b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs @@ -11,6 +11,11 @@ namespace Umbraco.Web.Trees [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] internal class CoreTreeAttribute : Attribute { + public string TreeGroup { get; set; } + public CoreTreeAttribute() + { + + } } } From 2612f3328333a0b4e6d45500e458bc0a3e7fa8a0 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 5 Oct 2018 13:11:38 +0100 Subject: [PATCH 035/585] Update trees using [CoreTree] attribute to use new property from our constants --- src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs | 2 +- src/Umbraco.Web/Trees/ContentTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/DataTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/DictionaryTreeController.cs | 2 +- src/Umbraco.Web/Trees/LanguageTreeController.cs | 2 +- src/Umbraco.Web/Trees/MediaTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/PartialViewsTreeController.cs | 2 +- src/Umbraco.Web/Trees/TemplatesTreeController.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index 145a0f5947..51b94098ba 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Trees [UmbracoApplicationAuthorize(Constants.Applications.Content)] [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 10)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class ContentBlueprintTreeController : TreeController { diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index da1eae7b2c..0f395b10fc 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 0)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class ContentTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 183889777b..231a06ce4e 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, null, sortOrder:7)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class DataTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/DictionaryTreeController.cs b/src/Umbraco.Web/Trees/DictionaryTreeController.cs index c2491fefe0..f3288245e5 100644 --- a/src/Umbraco.Web/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web/Trees/DictionaryTreeController.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Dictionary)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] [Tree(Constants.Applications.Settings, Constants.Trees.Dictionary, null, sortOrder: 3)] public class DictionaryTreeController : TreeController { diff --git a/src/Umbraco.Web/Trees/LanguageTreeController.cs b/src/Umbraco.Web/Trees/LanguageTreeController.cs index eadb5c50d0..304eb91386 100644 --- a/src/Umbraco.Web/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web/Trees/LanguageTreeController.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Languages)] [Tree(Constants.Applications.Settings, Constants.Trees.Languages, null, sortOrder: 5)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class LanguageTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 086c1a5194..074f480fce 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:9)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class MediaTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs index aaaeb2d175..4011c4efed 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, null, sortOrder: 2)] [UmbracoTreeAuthorize(Constants.Trees.PartialViews)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class PartialViewsTreeController : FileSystemTreeController { protected override IFileSystem FileSystem => Current.FileSystems.PartialViewsFileSystem; diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index d9aa0f21a0..1ffcdcbb99 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Templates)] [Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:1)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class TemplatesTreeController : TreeController, ISearchableTree { /// From 774f1d706c886f071df2f6c1e284911e2ad38e7c Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Thu, 4 Oct 2018 16:21:11 +0100 Subject: [PATCH 036/585] Fixed issue with actions dropdown menu being partially visible. --- src/Umbraco.Web.UI.Client/src/less/panel.less | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 51a2504363..4e26ab73b6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -368,7 +368,6 @@ flex-direction: column; height: 99px; padding: 0 20px; - overflow-y: hidden; } .umb-panel-header-content { From 0f3da1ae1446c9e1317b2e8888618a1f54710fec Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Mon, 8 Oct 2018 08:38:24 +0200 Subject: [PATCH 037/585] #3159 - Add border around the colors from the approved color picker (#3161) --- .../src/less/components/umb-color-swatches.less | 5 +++-- .../src/views/propertyeditors/colorpicker/colorpicker.html | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index df80aef2ce..d8e67444a1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -3,7 +3,7 @@ flex-flow: row wrap; .umb-color-box { - border: none; + border: 1px solid @gray-8; color: white; cursor: pointer; padding: 1px; @@ -69,7 +69,8 @@ margin-right: -1px; text-indent: 0; text-align: left; - border: 1px solid @gray-8; + border-top: 1px solid @gray-8; + border-bottom: 1px solid @gray-8; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; overflow: hidden; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html index ccea7519ac..b09d73cee2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html @@ -1,5 +1,6 @@ 
+
You haven't defined any colors
From 13693bddef38adee0652b3b6a52925c103e69d6d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 8 Oct 2018 10:54:02 +0200 Subject: [PATCH 038/585] Ensures the correct default list view settings are loaded for media content type --- .../ContentApps/ListViewContentAppDefinition.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs index 5c73b2fa8c..2b5e1a0910 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs @@ -58,8 +58,21 @@ namespace Umbraco.Web.ContentApps Weight = Weight }; + int dtdId; + switch(entityType) + { + case "content": + dtdId = Core.Constants.DataTypes.DefaultContentListView; + break; + case "media": + dtdId = Core.Constants.DataTypes.DefaultMediaListView; + break; + default: + throw new NotSupportedException($"Entity type {entityType} is not supported here."); + } + var customDtdName = Core.Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; - var dtdId = Core.Constants.DataTypes.DefaultContentListView; + //first try to get the custom one if there is one var dt = dataTypeService.GetDataType(customDtdName) ?? dataTypeService.GetDataType(dtdId); From 975e08ba9f0abe1b57a1cfe4c651c546ddbe0c62 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 8 Oct 2018 11:23:12 +0200 Subject: [PATCH 039/585] Fixes member/media list views, reformats some code and fixes js error --- .../views/member/member.list.controller.js | 2 +- .../listview/layouts/grid/grid.html | 136 ++++++++---------- .../ListViewContentAppDefinition.cs | 31 ++-- src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 4 +- src/Umbraco.Web/Editors/MemberController.cs | 2 +- 6 files changed, 82 insertions(+), 95 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js index 9fc03349e8..b099396142 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js @@ -39,7 +39,7 @@ function MemberListController($scope, $routeParams, $location, $q, $window, appS // route but there might be server validation errors in the collection which we need to display // after the redirect, so we will bind all subscriptions which will show the server validation errors // if there are any and then clear them so the collection no longer persists them. - serverValidationManager.executeAndClearAllSubscriptions(); + serverValidationManager.notifyAndClearAllSubscriptions(); $scope.page.loading = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index 29dd1b870f..6c7a9c7f06 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -1,91 +1,79 @@
-
+
- - + + - -
No content has been added
-
No members have been added
-
+ +
No content has been added
+
No members have been added
+
- - - + + + - - - + + + -
+
-
+
- - + + - - + + - - + + - -
    -
  • -
    {{ property.header }}
    -
    {{ vm.mediaDetailsTooltip.item[property.alias] }}
    -
  • -
-
+ +
    +
  • +
    {{ property.header }}
    +
    {{ vm.mediaDetailsTooltip.item[property.alias] }}
    +
  • +
+
- - - + + + - - - + + +
diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs index 2b5e1a0910..c5636d3a02 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs @@ -25,6 +25,7 @@ namespace Umbraco.Web.ContentApps public ContentApp GetContentAppFor(object o) { string contentTypeAlias, entityType; + int dtdId; switch (o) { @@ -33,22 +34,33 @@ namespace Umbraco.Web.ContentApps case IContent content: contentTypeAlias = content.ContentType.Alias; entityType = "content"; + dtdId = Core.Constants.DataTypes.DefaultContentListView; break; case IMedia media when !media.ContentType.IsContainer && media.ContentType.Alias != Core.Constants.Conventions.MediaTypes.Folder: return null; case IMedia media: contentTypeAlias = media.ContentType.Alias; entityType = "media"; + dtdId = Core.Constants.DataTypes.DefaultMediaListView; break; default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); } - return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias); + return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias, dtdId); } - public static ContentApp CreateContentApp(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors, string entityType, string contentTypeAlias) + public static ContentApp CreateContentApp(IDataTypeService dataTypeService, + PropertyEditorCollection propertyEditors, + string entityType, string contentTypeAlias, + int defaultListViewDataType) { + if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); + if (propertyEditors == null) throw new ArgumentNullException(nameof(propertyEditors)); + if (string.IsNullOrWhiteSpace(entityType)) throw new ArgumentException("message", nameof(entityType)); + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("message", nameof(contentTypeAlias)); + if (defaultListViewDataType == default) throw new ArgumentException("defaultListViewDataType", nameof(defaultListViewDataType)); + var contentApp = new ContentApp { Alias = "umbListView", @@ -58,24 +70,11 @@ namespace Umbraco.Web.ContentApps Weight = Weight }; - int dtdId; - switch(entityType) - { - case "content": - dtdId = Core.Constants.DataTypes.DefaultContentListView; - break; - case "media": - dtdId = Core.Constants.DataTypes.DefaultMediaListView; - break; - default: - throw new NotSupportedException($"Entity type {entityType} is not supported here."); - } - var customDtdName = Core.Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; //first try to get the custom one if there is one var dt = dataTypeService.GetDataType(customDtdName) - ?? dataTypeService.GetDataType(dtdId); + ?? dataTypeService.GetDataType(defaultListViewDataType); if (dt == null) { diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 1fcee6d727..adc2a78804 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -232,7 +232,7 @@ namespace Umbraco.Web.Editors public ContentItemDisplay GetRecycleBin() { var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content")); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content", Core.Constants.DataTypes.DefaultMembersListView)); apps[0].Active = true; var display = new ContentItemDisplay { diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 635cdfaa17..dc744ea361 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -99,7 +99,7 @@ namespace Umbraco.Web.Editors public MediaItemDisplay GetRecycleBin() { var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media")); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media", Core.Constants.DataTypes.DefaultMediaListView)); apps[0].Active = true; var display = new MediaItemDisplay { @@ -913,4 +913,4 @@ namespace Umbraco.Web.Editors return hasPathAccess; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 9f70c3c33b..6117db8857 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -139,7 +139,7 @@ namespace Umbraco.Web.Editors var name = foundType != null ? foundType.Name : listName; var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member")); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member", Core.Constants.DataTypes.DefaultMembersListView)); apps[0].Active = true; var display = new MemberListDisplay From a9756e065cb27d06a72124b1ed1c9f98b1423b70 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 8 Oct 2018 13:07:30 +0200 Subject: [PATCH 040/585] Don't show toggles to create templates when that feature is disabled. Also default the toggle to false when the feature is disabled. --- .../src/views/documenttypes/create.controller.js | 4 ++-- .../src/views/documenttypes/create.html | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 551a5d6ec1..b05cb52fe9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -27,8 +27,8 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.showCreateDocTypeCollection = function () { $scope.model.creatingDoctypeCollection = true; - $scope.model.collectionCreateTemplate = true; - $scope.model.collectionItemCreateTemplate = true; + $scope.model.collectionCreateTemplate = !$scope.model.disableTemplates; + $scope.model.collectionItemCreateTemplate = !$scope.model.disableTemplates; }; $scope.createContainer = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 98f9b23b64..3519195848 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -19,8 +19,8 @@ - - Document type> + + Document type> @@ -80,14 +80,18 @@ - - + + + + - - + + + + From 4d7a36e017772a3d9fd76a69ec7a3e5db24319d6 Mon Sep 17 00:00:00 2001 From: KimHolzmann Date: Mon, 8 Oct 2018 14:04:52 +0200 Subject: [PATCH 041/585] #3001 Styles cannot be saved - Not sure if oldName is legacy (#3123) --- .../WebServices/SaveFileController.cs | 50 +++++++++++-------- .../stylesheet/editstylesheet.aspx.cs | 4 +- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index fe93fb1e06..5f2fcaeb34 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -39,13 +39,13 @@ namespace Umbraco.Web.WebServices [HttpPost] public JsonResult SavePartialViewMacro(string filename, string oldName, string contents) { - var svce = (FileService) Services.FileService; + var svce = (FileService)Services.FileService; return SavePartialView(svce, filename, oldName, contents, "MacroPartials/", (s, n) => s.GetPartialViewMacro(n), - (s, v) => s.ValidatePartialViewMacro((PartialView) v), + (s, v) => s.ValidatePartialViewMacro((PartialView)v), (s, v) => s.SavePartialViewMacro(v)); } @@ -59,13 +59,13 @@ namespace Umbraco.Web.WebServices [HttpPost] public JsonResult SavePartialView(string filename, string oldName, string contents) { - var svce = (FileService) Services.FileService; + var svce = (FileService)Services.FileService; return SavePartialView(svce, filename, oldName, contents, "Partials/", (s, n) => s.GetPartialView(n), - (s, v) => s.ValidatePartialView((PartialView) v), + (s, v) => s.ValidatePartialView((PartialView)v), (s, v) => s.SavePartialView(v)); } @@ -77,9 +77,7 @@ namespace Umbraco.Web.WebServices Func> save) { // sanitize input - partial view names have an extension - filename = filename - .Replace('\\', '/') - .TrimStart('/'); + filename = CleanFilename(filename); // sharing the editor with partial views & partial view macros, // using path prefix to differenciate, @@ -98,7 +96,7 @@ namespace Umbraco.Web.WebServices oldname = oldname.TrimStart(pathPrefix); } - var currentView = oldname.IsNullOrWhiteSpace() + var currentView = oldname.IsNullOrWhiteSpace() ? get(svce, filename) : get(svce, oldname); @@ -108,7 +106,7 @@ namespace Umbraco.Web.WebServices currentView.Path = filename; currentView.Content = contents; - + Attempt attempt; @@ -166,7 +164,7 @@ namespace Umbraco.Web.WebServices if (Math.Max(t.MasterTemplate, 0) != Math.Max(masterTemplateId, 0)) { t.MasterTemplate = Math.Max(masterTemplateId, 0); - pathChanged = true; + pathChanged = true; } } catch (ArgumentException ex) @@ -206,11 +204,9 @@ namespace Umbraco.Web.WebServices public JsonResult SaveScript(string filename, string oldName, string contents) { // sanitize input - script names have an extension - filename = filename - .Replace('\\', '/') - .TrimStart('/'); + filename = CleanFilename(filename); - var svce = (FileService) Services.FileService; + var svce = (FileService)Services.FileService; var script = svce.GetScriptByName(oldName); if (script == null) script = new Script(filename); @@ -223,7 +219,7 @@ namespace Umbraco.Web.WebServices if (svce.ValidateScript(script) == false) return Failed(ui.Text("speechBubbles", "scriptErrorText"), ui.Text("speechBubbles", "scriptErrorHeader"), new FileSecurityException("File '" + filename + "' is not a valid script file.")); - + svce.SaveScript(script); } catch (Exception e) @@ -245,12 +241,18 @@ namespace Umbraco.Web.WebServices public JsonResult SaveStylesheet(string filename, string oldName, string contents) { // sanitize input - stylesheet names have no extension - filename = filename - .Replace('\\', '/') - .TrimStart('/') - .EnsureEndsWith(".css"); + var svce = (FileService)Services.FileService; + + filename = CleanFilename(filename); + oldName = CleanFilename(oldName); + + if (filename != oldName) + { + var stylesheetExists = svce.GetStylesheetByName(filename); + if (stylesheetExists != null) + return Failed(ui.Text("speechBubbles", "cssErrorText"), "A file named '" + filename + ".css' already exists."); + } - var svce = (FileService) Services.FileService; var stylesheet = svce.GetStylesheetByName(oldName); if (stylesheet == null) stylesheet = new Stylesheet(filename); @@ -281,6 +283,14 @@ namespace Umbraco.Web.WebServices }); } + private static string CleanFilename(string filename) + { + return filename + .Replace('\\', '/') + .TrimStart('/') + .EnsureEndsWith(".css"); + } + /// /// Returns a successful message /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs index 63dec9111a..b9a62fde8f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs @@ -63,7 +63,7 @@ namespace umbraco.cms.presentation.settings.stylesheet TreeSyncPath = DeepLink.GetTreePathFromFilePath(filename).TrimEnd(".css"); // name derives from path, without the .css extension, clean for xss - NameTxt.Text = stylesheet.Path.TrimEnd(".css").CleanForXss('\\', '/'); + NameTxt.Text = stylesheet.Path.TrimEnd(".css").CleanForXss('\\', '/').Replace("\\", "/"); if (IsPostBack == false) { @@ -154,4 +154,4 @@ namespace umbraco.cms.presentation.settings.stylesheet protected global::umbraco.uicontrols.CodeArea editorSource; } -} \ No newline at end of file +} From a3cf1d7d582d6be71fc1c0fb11f54a73c651a43f Mon Sep 17 00:00:00 2001 From: Kim Holzmann Date: Thu, 4 Oct 2018 08:25:51 +0200 Subject: [PATCH 042/585] #3138 - Can add a null item to the grid Don't perform submit function if selectedItem is undefined --- .../src/views/propertyeditors/grid/grid.controller.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index dbfbdbdcad..5e08c5e6b4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -281,10 +281,12 @@ angular.module("umbraco") availableItems: area.$allowedEditors, event: event, show: true, - submit: function(model) { - $scope.addControl(model.selectedItem, area, index); - $scope.editorOverlay.show = false; - $scope.editorOverlay = null; + submit: function (model) { + if (model.selectedItem) { + $scope.addControl(model.selectedItem, area, index); + $scope.editorOverlay.show = false; + $scope.editorOverlay = null; + } } }; }; From b820baf64350c31808922223cd58f714d965a713 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 8 Oct 2018 14:27:18 +0200 Subject: [PATCH 043/585] Whe moving an item, make sure the current user is registered in Audit instead of the default user (#3150) --- src/Umbraco.Web/Editors/ContentController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 1938ac16c2..1a1957cc28 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -546,9 +546,9 @@ namespace Umbraco.Web.Editors EnsureUniqueName(name, content, "name"); - var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.GetUserId()); + var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id); - Services.ContentService.SaveBlueprint(blueprint, Security.GetUserId()); + Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id); var notificationModel = new SimpleNotificationModel(); notificationModel.AddSuccessNotification( @@ -755,7 +755,7 @@ namespace Umbraco.Web.Editors return HandleContentNotFound(id, false); } - var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.GetUserId()); + var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.CurrentUser.Id); if (publishResult.Success == false) { var notificationModel = new SimpleNotificationModel(); @@ -808,7 +808,7 @@ namespace Umbraco.Web.Editors //if the current item is in the recycle bin if (foundContent.IsInRecycleBin() == false) { - var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.GetUserId()); + var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.CurrentUser.Id); if (moveResult == false) { //returning an object of INotificationModel will ensure that any pending @@ -818,7 +818,7 @@ namespace Umbraco.Web.Editors } else { - var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.GetUserId()); + var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.CurrentUser.Id); if (deleteResult == false) { //returning an object of INotificationModel will ensure that any pending @@ -895,7 +895,7 @@ namespace Umbraco.Web.Editors { var toMove = ValidateMoveOrCopy(move); - Services.ContentService.Move(toMove, move.ParentId); + Services.ContentService.Move(toMove, move.ParentId, Security.CurrentUser.Id); var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); From 7db13960e9c665ea69b9f177bc6f7a8314e0b330 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:32:12 +0100 Subject: [PATCH 044/585] Adds in GetAllTypes to ApplicationTreeService --- src/Umbraco.Core/Services/IApplicationTreeService.cs | 11 ++++++++++- src/Umbraco.Web/Services/ApplicationTreeService.cs | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/IApplicationTreeService.cs b/src/Umbraco.Core/Services/IApplicationTreeService.cs index 5b6976c021..98c3047fea 100644 --- a/src/Umbraco.Core/Services/IApplicationTreeService.cs +++ b/src/Umbraco.Core/Services/IApplicationTreeService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Services @@ -41,6 +42,9 @@ namespace Umbraco.Core.Services /// Returns a ApplicationTree Array IEnumerable GetAll(); + + IEnumerable GetAllTypes(); + /// /// Gets the application tree for the applcation with the specified alias /// @@ -113,6 +117,11 @@ namespace Umbraco.Core.Services throw new System.NotImplementedException(); } + public IEnumerable GetAllTypes() + { + throw new System.NotImplementedException(); + } + /// /// Gets the application tree for the applcation with the specified alias /// diff --git a/src/Umbraco.Web/Services/ApplicationTreeService.cs b/src/Umbraco.Web/Services/ApplicationTreeService.cs index abd9f08a53..08a17187e7 100644 --- a/src/Umbraco.Web/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Web/Services/ApplicationTreeService.cs @@ -21,6 +21,7 @@ namespace Umbraco.Web.Services private readonly ILogger _logger; private readonly CacheHelper _cache; private Lazy> _allAvailableTrees; + private IEnumerable _treeTypes; internal const string TreeConfigFileName = "trees.config"; private static string _treeConfig; private static readonly object Locker = new object(); @@ -251,6 +252,11 @@ namespace Umbraco.Web.Services return GetAppTrees().OrderBy(x => x.SortOrder); } + public IEnumerable GetAllTypes() + { + return _treeTypes ?? (_treeTypes = GetAll().Select(x => x.GetRuntimeType())); + } + /// /// Gets the application tree for the applcation with the specified alias /// From 6c7c510eb98495db0ddf94fd7200c2953be93fab Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:33:14 +0100 Subject: [PATCH 045/585] Initial WIP of grouping trees for settings section - needs tidying --- .../Trees/ApplicationTreeController.cs | 106 +++++++++++++++++- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index d489421353..0b9ca64ca9 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; @@ -8,6 +9,7 @@ using System.Web.Http; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -20,6 +22,14 @@ namespace Umbraco.Web.Trees [PluginController("UmbracoTrees")] public class ApplicationTreeController : UmbracoAuthorizedApiController { + private static readonly Lazy>> CoreTrees + = new Lazy>>(() => + Current.Services.ApplicationTreeService.GetAllTypes() + .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute(false)?.TreeGroup)) + .GroupBy(x => x.TreeGroup) + .ToList()); + + /// /// Returns the tree nodes for an application /// @@ -29,10 +39,12 @@ namespace Umbraco.Web.Trees /// An optional bool (defaults to true), if set to false it will also load uninitialized trees /// [HttpQueryStringFilter("queryStrings")] - public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) + public async Task> GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) { application = application.CleanForXss(); + var rootNodeGroups = new List(); + if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound); var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); @@ -56,7 +68,10 @@ namespace Umbraco.Web.Trees //this will be null if it cannot convert to ta single root section if (result != null) - return result; + { + rootNodeGroups.Add(result); + return rootNodeGroups; + } } var collection = new TreeNodeCollection(); @@ -71,9 +86,90 @@ namespace Umbraco.Web.Trees } } - var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); - multiTree.Name = Services.TextService.Localize("sections/"+ application); - return multiTree; + //Don't apply fancy grouping logic futher down, if we are not 'settings' section + if(application != Constants.Applications.Settings) + { + var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); + multiTree.Name = Services.TextService.Localize("sections/" + application); + + rootNodeGroups.Add(multiTree); + return rootNodeGroups; + } + + //For settings section only + //Group trees by [CoreTree] attribute + + //Core Trees contains all trees for all sections/applications + foreach(var treeSectionGroup in CoreTrees.Value) + { + var treeGroupName = treeSectionGroup.Key; + if (treeGroupName == null) + { + //This is third party trees + //Where user definied or [CoreTree] with no group is set + var thirdPartyNodes = new TreeNodeCollection(); + + //Only add trees to a new collection if they are from 'settings' + foreach(var thirdPartyTree in treeSectionGroup) + { + //Item1 is the type + var thirdPartyType = thirdPartyTree.Item1; + + var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == thirdPartyType); + if (findAppTree != null) + { + //Now we need to get the 'TreeNode' which is in 'collection' + var thirdPartyTreeNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); + + if(thirdPartyTreeNode != null) + { + //Add to a new list/collection + thirdPartyNodes.Add(thirdPartyTreeNode); + } + } + } + + //Compared all 'null' grouped trees with appTreeTypes + //Create third party node collection + var thirdPartyRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, thirdPartyNodes); + thirdPartyRoot.Name = "Third Party WARREN"; + + rootNodeGroups.Add(thirdPartyRoot); + } + else + { + var groupNodeCollection = new TreeNodeCollection(); + + //Only add trees to a new collection if they are from 'settings' + foreach (var thirdPartyTree in treeSectionGroup) + { + //Item1 is the type + var thirdPartyType = thirdPartyTree.Item1; + + var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == thirdPartyType); + if (findAppTree != null) + { + //Now we need to get the 'TreeNode' which is in 'collection' + var thirdPartyTreeNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); + + if (thirdPartyTreeNode != null) + { + //Add to a new list/collection + groupNodeCollection.Add(thirdPartyTreeNode); + } + } + } + + //Compared all 'null' grouped trees with appTreeTypes + //Create third party node collection + var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); + groupRoot.Name = treeGroupName; + + rootNodeGroups.Add(groupRoot); + } + } + + return rootNodeGroups; } /// From 5a551bd3d60be735b2e3ae406d87ea3df19729a6 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:34:11 +0100 Subject: [PATCH 046/585] Changed needed as GetApplicationTrees returns an IEnumerable of SectionRootNodes now --- src/Umbraco.Web/Editors/SectionController.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 03921b396c..7c3eb94059 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -50,9 +50,13 @@ namespace Umbraco.Web.Editors { //get the first tree in the section and get it's root node route path var sectionTrees = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; - section.RoutePath = sectionTrees.IsContainer == false || sectionTrees.Children.Count == 0 - ? sectionTrees.RoutePath - : sectionTrees.Children[0].RoutePath; + + //Root node trees are now in collection + var firstTree = sectionTrees.FirstOrDefault(); + + section.RoutePath = firstTree.IsContainer == false || firstTree.Children.Count == 0 + ? firstTree.RoutePath + : firstTree.Children[0].RoutePath; } } From b3f4e9da8ffb07652bb386105e5139d373b4a119 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:36:14 +0100 Subject: [PATCH 047/585] JS tweaks needed to deal with a collection of SectionRootNodes returning from the API --- .../src/common/services/tree.service.js | 92 ++++++++++--------- .../src/controllers/navigation.controller.js | 14 ++- 2 files changed, 59 insertions(+), 47 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index d5d2093d3b..a5bbf2f886 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -15,21 +15,21 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS // a tab and have the trees where they used to be - supposed that is kind of nice but would mean we'd have to store the parent // as a nodeid reference instead of a variable with a getParent() method. var treeCache = {}; - + var standardCssClass = 'icon umb-tree-icon sprTree'; function getCacheKey(args) { //if there is no cache key they return null - it won't be cached. if (!args || !args.cacheKey) { return null; - } + } var cacheKey = args.cacheKey; cacheKey += "_" + args.section; return cacheKey; } - return { + return { /** Internal method to return the tree cache */ _getTreeCache: function() { @@ -70,10 +70,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } }); }, - + /** Internal method that ensures there's a routePath, parent and level property on each tree node and adds some icon specific properties so that the nodes display properly */ _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) { - //if no level is set, then we make it 1 + //if no level is set, then we make it 1 var childLevel = (level ? level : 1); //set the section if it's not already set if (!parentNode.section) { @@ -91,13 +91,14 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var funcParent = function() { return parentNode; }; + for (var i = 0; i < treeNodes.length; i++) { var treeNode = treeNodes[i]; treeNode.level = childLevel; - //create a function to get the parent node, we could assign the parent node but + //create a function to get the parent node, we could assign the parent node but // then we cannot serialize this entity because we have a cyclical reference. // Instead we just make a function to return the parentNode. treeNode.parent = funcParent; @@ -108,17 +109,17 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //if there is not route path specified, then set it automatically, //if this is a tree root node then we want to route to the section's dashboard if (!treeNode.routePath) { - + if (treeNode.metaData && treeNode.metaData["treeAlias"]) { //this is a root node - treeNode.routePath = section; + treeNode.routePath = section; } else { var treeAlias = this.getTreeAlias(treeNode); treeNode.routePath = section + "/" + treeAlias + "/edit/" + treeNode.id; } } - + //now, format the icon data if (treeNode.iconIsClass === undefined || treeNode.iconIsClass) { var converted = iconHelper.convertFromLegacyTreeNodeIcon(treeNode); @@ -155,10 +156,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @description * Determines if the current tree is a plugin tree and if so returns the package folder it has declared * so we know where to find it's views, otherwise it will just return undefined. - * + * * @param {String} treeAlias The tree alias to check */ - getTreePackageFolder: function(treeAlias) { + getTreePackageFolder: function(treeAlias) { //we determine this based on the server variables if (Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.trees && @@ -167,7 +168,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function(item) { return item.alias === treeAlias; }); - + return found ? found.packageFolder : undefined; } return undefined; @@ -181,7 +182,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * * @description * Clears the tree cache - with optional cacheKey, optional section or optional filter. - * + * * @param {Object} args arguments * @param {String} args.cacheKey optional cachekey - this is used to clear specific trees in dialogs * @param {String} args.section optional section alias - clear tree for a given section @@ -205,7 +206,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!args.cacheKey) { throw "args.cacheKey is required if args.childrenOf is supplied"; } - //this will clear out all children for the parentId passed in to this parameter, we'll + //this will clear out all children for the parentId passed in to this parameter, we'll // do this by recursing and specifying a filter var self = this; this.clearCache({ @@ -238,7 +239,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //set the result to the filtered data treeCache[args.cacheKey] = result; } - else { + else { //remove the cache treeCache = _.omit(treeCache, args.cacheKey); } @@ -261,7 +262,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return k.endsWith("_" + args.section); }); treeCache = _.omit(treeCache, toRemove2); - } + } } }, @@ -285,7 +286,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!args.node) { throw "No node defined on args object for loadNodeChildren"; } - + this.removeChildNodes(args.node); args.node.loading = true; @@ -312,7 +313,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //in case of error, emit event eventsService.emit("treeService.treeNodeLoadError", {error: reason } ); - //stop show the loading indicator + //stop show the loading indicator args.node.loading = false; //tell notications about the error @@ -342,9 +343,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS throw "Cannot remove a node that doesn't have a parent"; } //remove the current item from it's siblings - treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); + treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); }, - + /** * @ngdoc method * @name umbraco.services.treeService#removeChildNodes @@ -352,7 +353,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * Removes all child nodes from a given tree node + * Removes all child nodes from a given tree node * @param {object} treeNode the node to remove children from */ removeChildNodes : function(treeNode) { @@ -426,7 +427,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (found) { return found; } - + //check each child of this node if (!treeNode.children) { return null; @@ -442,7 +443,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } } } - + //not found return found === undefined ? null : found; }, @@ -464,9 +465,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //all root nodes have metadata key 'treeAlias' var root = null; - var current = treeNode; + var current = treeNode; while (root === null && current) { - + if (current.metaData && current.metaData["treeAlias"]) { root = current; } @@ -491,7 +492,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node + * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node * @param {object} treeNode to retrive tree alias from */ getTreeAlias : function(treeNode) { @@ -509,7 +510,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * gets the tree, returns a promise + * gets the tree, returns a promise * @param {object} args Arguments * @param {string} args.section Section alias * @param {string} args.cacheKey Optional cachekey @@ -525,7 +526,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } var cacheKey = getCacheKey(args); - + //return the cache if it exists if (cacheKey && treeCache[cacheKey] !== undefined) { return $q.when(treeCache[cacheKey]); @@ -540,8 +541,13 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS alias: args.section, root: data }; - //we need to format/modify some of the node data to be used in our app. - self._formatNodeDataForUseInUI(result.root, result.root.children, args.section); + + for (var i = 0; i < result.root.length; i++) { + var group = result.root[i]; + + //we need to format/modify some of the node data to be used in our app. + self._formatNodeDataForUseInUI(group, group.children, args.section); + } //cache this result if a cache key is specified - generally a cache key should ONLY // be specified for application trees, dialog trees should not be cached. @@ -584,7 +590,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return data; }); }, - + /** * @ngdoc method * @name umbraco.services.treeService#getChildren @@ -592,7 +598,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * Gets the children from the server for a given node + * Gets the children from the server for a given node * @param {object} args Arguments * @param {object} args.node tree node object to retrieve the children for * @param {string} args.section current section alias @@ -618,7 +624,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return $q.when(data); }); }, - + /** * @ngdoc method * @name umbraco.services.treeService#reloadNode @@ -639,7 +645,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!node.section) { throw "cannot reload a single node without an assigned node.section"; } - + //set the node to loading node.loading = true; @@ -663,7 +669,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //just update as per normal - this means styles, etc.. won't be applied _.extend(node.parent().children[index], found); } - + //set the node loading node.parent().children[index].loading = false; //return @@ -684,12 +690,12 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * This will return the current node's path by walking up the tree + * This will return the current node's path by walking up the tree * @param {object} node Tree node to retrieve path for */ getPath: function(node) { if (!node) { - throw "node cannot be null"; + throw "node cannot be null"; } if (!angular.isFunction(node.parent)) { throw "node.parent is not a function, the path cannot be resolved"; @@ -698,7 +704,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var reversePath = []; var current = node; while (current != null) { - reversePath.push(current.id); + reversePath.push(current.id); if (current.metaData && current.metaData["treeAlias"]) { current = null; } @@ -710,7 +716,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS }, syncTree: function(args) { - + if (!args) { throw "No args object defined for syncTree"; } @@ -733,7 +739,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!root) { throw "Could not get the root tree node based on the node passed in"; } - + //now we want to loop through the ids in the path, first we'll check if the first part //of the path is the root node, otherwise we'll search it's children. var currPathIndex = 0; @@ -748,7 +754,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS currPathIndex = 1; } } - + //now that we have the first id to lookup, we can start the process var self = this; @@ -778,7 +784,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } } else { - //couldn't find it in the + //couldn't find it in the return self.loadNodeChildren({ node: node, section: node.section }).then(function (children) { //ok, got the children, let's find it var found = self.getChildNode(node, args.path[currPathIndex]); @@ -810,7 +816,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return doSync(); } - + }; } diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 1f6f2c75b8..dcc17612b3 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -184,9 +184,15 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //Listen for section state changes evts.push(eventsService.on("appState.treeState.changed", function (e, args) { - var f = args; - if (args.value.root && args.value.root.metaData.containsTrees === false) { - $rootScope.emptySection = true; + if (args.value.root.length > 0) + { + for (var i = 0; i < args.value.root.length; i++) { + var group = args.value.root[i]; + + if(group.metaData.containsTrees === false){ + $rootScope.emptySection = true; + } + } } else { $rootScope.emptySection = false; @@ -422,7 +428,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //this reacts to the options item in the tree //TODO: migrate to nav service - //TODO: is this used? + //TODO: is this used? $scope.searchShowMenu = function (ev, args) { //always skip default args.skipDefault = true; From c306bdea47a2ed4777ab7886727c9c7d9c67854f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:36:59 +0100 Subject: [PATCH 048/585] Updates umb-tree directive to loop over the uodated WebAPI collection of SectionRootNodes --- .../src/views/components/tree/umb-tree.html | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html index 74c1dc2c99..3713312968 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html @@ -1,19 +1,21 @@ \ No newline at end of file From be42783616a994272f2b28002ccb9c5de864a625 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 14:10:11 +0100 Subject: [PATCH 049/585] Remove dupe code & order by descending (still need to specify own sort order) --- .../Trees/ApplicationTreeController.cs | 90 +++++++------------ 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 0b9ca64ca9..7eeb19893e 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -27,6 +27,7 @@ namespace Umbraco.Web.Trees Current.Services.ApplicationTreeService.GetAllTypes() .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute(false)?.TreeGroup)) .GroupBy(x => x.TreeGroup) + .OrderByDescending(x => x.Key) .ToList()); @@ -103,70 +104,41 @@ namespace Umbraco.Web.Trees foreach(var treeSectionGroup in CoreTrees.Value) { var treeGroupName = treeSectionGroup.Key; + + var groupNodeCollection = new TreeNodeCollection(); + + //Only add trees to a new collection if they are from 'settings' + foreach (var treeItem in treeSectionGroup) + { + //Item1 tuple - is the type from all tree types + var treeItemType = treeItem.Item1; + + var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == treeItemType); + if (findAppTree != null) + { + //Now we need to get the 'TreeNode' which is in 'collection' + var treeItemNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); + + if (treeItemNode != null) + { + //Add to a new list/collection + groupNodeCollection.Add(treeItemNode); + } + } + } + + //If treeGroupName == null then its third party if (treeGroupName == null) { - //This is third party trees - //Where user definied or [CoreTree] with no group is set - var thirdPartyNodes = new TreeNodeCollection(); - - //Only add trees to a new collection if they are from 'settings' - foreach(var thirdPartyTree in treeSectionGroup) - { - //Item1 is the type - var thirdPartyType = thirdPartyTree.Item1; - - var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == thirdPartyType); - if (findAppTree != null) - { - //Now we need to get the 'TreeNode' which is in 'collection' - var thirdPartyTreeNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); - - if(thirdPartyTreeNode != null) - { - //Add to a new list/collection - thirdPartyNodes.Add(thirdPartyTreeNode); - } - } - } - - //Compared all 'null' grouped trees with appTreeTypes - //Create third party node collection - var thirdPartyRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, thirdPartyNodes); - thirdPartyRoot.Name = "Third Party WARREN"; - - rootNodeGroups.Add(thirdPartyRoot); + //This is used for the localisation key + //treeHeaders/thirdPartyGroup + treeGroupName = "thirdPartyGroup"; } - else - { - var groupNodeCollection = new TreeNodeCollection(); - //Only add trees to a new collection if they are from 'settings' - foreach (var thirdPartyTree in treeSectionGroup) - { - //Item1 is the type - var thirdPartyType = thirdPartyTree.Item1; + var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); + groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); - var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == thirdPartyType); - if (findAppTree != null) - { - //Now we need to get the 'TreeNode' which is in 'collection' - var thirdPartyTreeNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); - - if (thirdPartyTreeNode != null) - { - //Add to a new list/collection - groupNodeCollection.Add(thirdPartyTreeNode); - } - } - } - - //Compared all 'null' grouped trees with appTreeTypes - //Create third party node collection - var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); - groupRoot.Name = treeGroupName; - - rootNodeGroups.Add(groupRoot); - } + rootNodeGroups.Add(groupRoot); } return rootNodeGroups; From 2e135d2006212df799765552bc0e916b9711cee9 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 4 Oct 2018 20:27:06 +0200 Subject: [PATCH 050/585] Register an audit on the parent node when sorting child nodes. --- src/Umbraco.Core/Services/ContentService.cs | 13 ++++++++----- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 ++ src/Umbraco.Web/Editors/ContentController.cs | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 2f8b9c8340..9b0de40090 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2060,12 +2060,15 @@ namespace Umbraco.Core.Services using (new WriteLock(Locker)) { var allContent = GetByIds(ids).ToDictionary(x => x.Id, x => x); - var items = ids.Select(x => allContent[x]); + if (allContent.Any() == false) + { + return false; + } + var items = ids.Select(x => allContent[x]).ToArray(); using (var uow = UowProvider.GetUnitOfWork()) { - var asArray = items.ToArray(); - var saveEventArgs = new SaveEventArgs(asArray); + var saveEventArgs = new SaveEventArgs(items); if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) { uow.Commit(); @@ -2075,7 +2078,7 @@ namespace Umbraco.Core.Services var repository = RepositoryFactory.CreateContentRepository(uow); var i = 0; - foreach (var content in asArray) + foreach (var content in items) { //If the current sort order equals that of the content //we don't need to update it, so just increment the sort order @@ -2122,7 +2125,7 @@ namespace Umbraco.Core.Services _publishingStrategy.PublishingFinalized(uow, shouldBePublished, false); } - Audit(uow, AuditType.Sort, "Sorting content performed by user", userId, 0); + Audit(uow, AuditType.Sort, "Sort child items performed by user", userId, items.First().ParentId); uow.Commit(); } } diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 71195be6f8..0e9fc09bcd 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -147,6 +147,7 @@ Brugeren har tilbagerullet indholdet til en tidligere tilstand Brugeren har sendt indholdet til udgivelse Brugeren har sendt indholdet til oversættelse + Brugeren har sorteret de underliggende sider Kopieret Udgivet Flyttet @@ -156,6 +157,7 @@ Indhold tilbagerullet Sendt til udgivelse Sendt til oversættelse + Sorteret For at skifte det valgte indholds dokumenttype, skal du først vælge en ny dokumenttype, som er gyldig på denne placering. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f12d509416..477bce0931 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -153,6 +153,7 @@ Content rollback performed by user Content Send To Publish performed by user Content Send To Translation performed by user + Sort child items performed by user Copy Publish Move @@ -162,6 +163,7 @@ Rollback Send To Publish Send To Translation + Sort To change the document type for the selected content, first select from the list of valid types for this location. 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 c6a6aadd77..b1fa2b84b3 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -153,6 +153,7 @@ Content rollback performed by user Content Send To Publish performed by user Content Send To Translation performed by user + Sort child items performed by user Copy Publish Move @@ -162,6 +163,7 @@ Rollback Send To Publish Send To Translation + Sort diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 1a1957cc28..6268759e29 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -871,7 +871,7 @@ namespace Umbraco.Web.Editors var contentService = Services.ContentService; // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sorted.IdSortOrder) == false) + if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) { LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); From b2bc259bd567f18343f8cf024664b051d420c698 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 14:19:52 +0100 Subject: [PATCH 051/585] Adds in lang keys --- src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/es.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/he.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/it.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml | 3 +++ src/Umbraco.Web.UI/umbraco/config/lang/nb.xml | 3 +++ src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml | 3 +++ 20 files changed, 60 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index 3471203e76..e554b45672 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -986,6 +986,9 @@ Oprávnění Uživatele Typy Uživatelů Uživatelé + + + Nová aktualizace je připrvena diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index e0dd0973f6..f1ecff48aa 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1452,6 +1452,9 @@ Mange hilsner fra Umbraco robotten Brugertilladelser Bruger Typer Brugere + + + Ny opdatering er klar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index c701e439ea..c02da3924d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -979,6 +979,9 @@ Ihr freundlicher Umbraco-Robot Berechtigungen Benutzertypen Benutzer + + + Neues Update verfügbar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 5b8dc0982c..d0c9685304 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1831,6 +1831,9 @@ To manage your website, simply open the Umbraco back office and start adding con Templates Analytics Users + Settings + Templating + Third Party New update ready 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 e449e798f0..01a30d74ae 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1859,6 +1859,9 @@ To manage your website, simply open the Umbraco back office and start adding con Templates Analytics Users + Settings + Templating + Third Party New update ready diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index dda74e00ed..717506f0a6 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -1520,6 +1520,9 @@ Plantillas Analíticas Usuarios + + + Existe una nueva actualización diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 6a1f9a58e7..d88fa30706 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -1818,6 +1818,9 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Analytique Utilisateurs Analytique + + + Nouvelle mise à jour disponible diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml index 911e0f4131..f22f1c3f19 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml @@ -888,6 +888,9 @@ To manage your website, simply open the Umbraco back office and start adding con הרשאות משתמש משתמש מקליד משתמש + + + עידכון חדש זמין diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml index 1f661000b6..89a03721ba 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml @@ -878,6 +878,9 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Permessi Utente Tipi di Utente Utenti + + + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index d3a4a5c27a..561e6ac1fc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -1185,6 +1185,9 @@ Runwayをインストールして作られた新しいウェブサイトがど ユーザーの権限 ユーザータイプ ユーザー + + + 新しい更新があります diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml index a7ab31efc6..b7aa8b5bb5 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml @@ -871,6 +871,9 @@ 사용자권한 사용자 유형 사용자 + + + 새 업데이트가 준비되었습니다. diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index 72d4afbeca..c7b15f678e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -1296,6 +1296,9 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Stylesheets Sjablonen Analytics + + + Nieuwe update beschikbaar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index 5f679a994a..bfb5fe74db 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -1496,6 +1496,9 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Analizy Częściowe Widoki Pliki Makro Częściowych Widoków + + + Aktualizacja jest gotowa diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml index a62b51f76f..47f87291e1 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml @@ -854,6 +854,9 @@ Para fechar a tarefa de tradução vá até os detalhes e clique no botão "Fech Permissões de usuário Tipos de Usuários Usuários + + + Nova atualização pronta diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 6cdfa16fcd..21b3cdce18 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -1839,6 +1839,9 @@ Стили CSS Шаблоны Пользователи + + + Доступны обновления diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index 83e1405560..328169ade3 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -901,6 +901,9 @@ Användarrättigheter Användartyper Användare + + + Ny uppdatering tillgänglig diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index a69300dbfb..79af1b31e8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -1073,6 +1073,9 @@ To manage your website, simply open the Umbraco back office and start adding con Stil dosyaları Şablonlar Analitikler + + + Yeni bir güncelleme geldi diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index 64ad3eab1c..80d01ac1af 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -1242,6 +1242,9 @@ Users 分部视图 分部视图宏文件 + + + 有可用更新 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml index 235177c712..be99f6b561 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml @@ -971,6 +971,9 @@ Vennlig hilsen Umbraco roboten Brukertillatelser Brukertyper typer Brukere + + + Ny oppdatering er klar diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml index a4c6fc7de5..93f6f5a10f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -1178,6 +1178,9 @@ 樣式表 範本 統計 + + + 有可用更新 From 3977d24eb2c8583d3ac2499a582c6d0d9f394412 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Mon, 8 Oct 2018 14:29:09 +0100 Subject: [PATCH 052/585] Button styling on inactive user profile & clean up user view (#3170) --- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../components/users/umb-user-details.less | 122 +++++++++++++++++ .../src/views/users/user.html | 4 +- .../src/views/users/views/user/details.html | 123 +++++++++--------- 4 files changed, 187 insertions(+), 63 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 2eaabe7842..eaf0b60707 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -146,6 +146,7 @@ @import "components/umb-mini-editor.less"; @import "components/users/umb-user-cards.less"; +@import "components/users/umb-user-details.less"; @import "components/users/umb-user-group-picker-list.less"; @import "components/users/umb-user-group-preview.less"; @import "components/users/umb-user-preview.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less new file mode 100644 index 0000000000..837506f312 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less @@ -0,0 +1,122 @@ +.umb-user-details-avtar { + margin-bottom: 20px; + padding-bottom: 20px; + border-bottom: 1px solid #d8d7d9; +} + +div.umb-user-details-actions > div { + margin-bottom: 20px; +} + +.umb-user-details-actions .umb-button { + margin-bottom: 20px; +} + +.umb-user-details-view-title { + font-size: 20px; + font-weight: bold; + color: @black; + margin-bottom: 30px; +} + +.umb-user-details-view-wrapper { + padding: 20px 60px; +} + +@media (max-width: 768px) { + + .umb-user-details-view-wrapper { + padding: 0; + } +} + +.umb-user-details-section { + margin-bottom: 40px; +} +.umb-user-details-details { + display: flex; +} + +a.umb-user-details-details__back-link { + font-weight: bold; + color: @black; +} + +.umb-user-details-details__back-link:hover { + color: @gray-4; + text-decoration: none; +} + + +@sidebarwidth: 350px; // Width of sidebar. Ugly hack because of old version of Less + +.umb-user-details-details__main-content { + flex: 1 1 auto; + margin-right: 30px; + width: calc(~'100%' - ~'@{sidebarwidth}' - ~'30px'); // Make sure that the main content area doesn't gets affected by inline styling +} + +.umb-user-details-details__main-content .umb-node-preview-add { + max-width: 100%; +} + + +.umb-user-details-details__sidebar { + flex: 0 0 @sidebarwidth; +} + +@media (max-width: 768px) { + + .umb-user-details-details { + flex-direction: column; + } + + .umb-user-details-details__main-content { + flex: 1 1 auto; + width: 100%; + margin-bottom: 30px; + margin-right: 0; + } + + .umb-user-details-details__sidebar { + flex: 1 1 auto; + width: 100%; + } +} + +.umb-user-details-details__section { + background: @gray-10; + padding: 20px; + margin-bottom: 20px; + border-radius: 3px; + border: 1px solid @gray-8; +} + +.umb-user-details-details__section-title { + font-size: 17px; + font-weight: bold; + color: @black; + margin-top: 0; + margin-bottom: 15px; +} + +.umb-user-details-details__section-description { + font-size: 12px; + line-height: 1.6em; + margin-bottom: 15px; +} + +.umb-user-details-details__information-item { + margin-bottom: 10px; + font-size: 13px; +} + +.umb-user-details-details__information-item-label { + color: @black; + font-weight: bold; +} + +.umb-user-details-details__information-item-content { + word-break: break-word; +} + 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 32c1c3d641..3e7b2b8d8d 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 @@ -
+
-
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index 7c93f1fd84..15a75c30b2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -1,6 +1,6 @@ -
+
-
+
@@ -95,8 +95,8 @@ Add @@ -121,9 +121,9 @@ Add @@ -165,12 +165,12 @@
-
+
-
+
-
+
-
+
-
+
-
+
- -
+
- - - - - - +
+ + +
+
+ + +
@@ -293,26 +294,26 @@
-
-
+
+
Status:
-
+
{{model.user.userDisplayState.name}}
-
+
+ type="text" + class="input-block-level" + localize="placeholder" + placeholder="@placeholders_enterMessage" + ng-model="model.resendInviteMessage" + rows="4"> +
-
-
+
+
Last login:
-
+
{{ model.user.formattedLastLogin }} {{ model.user.name | umbWordLimit:1 }} has not logged in yet
-
-
+
+
Failed login attempts:
-
+
{{ model.user.failedPasswordAttempts }}
-
-
+
+
Last lockout date:
-
+
{{ model.user.name | umbWordLimit:1 }} hasn't been locked out @@ -354,11 +355,11 @@
-
-
+
+
Password is last changed:
-
+
The password hasn't been changed @@ -366,20 +367,20 @@
-
-
+
+
User is created:
-
+
{{ model.user.formattedCreateDate }}
-
-
+
+
User is last updated:
-
+
{{ model.user.formattedUpdateDate }}
From 94922ccbd2118e0d51088b1751ce50ce1f2df7e2 Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Tue, 9 Oct 2018 08:51:02 +0200 Subject: [PATCH 053/585] #3141 fixed the problem differently after input from @markadrake --- .../src/common/services/navigation.service.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 8fb7137e9a..e617f62ed4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -73,6 +73,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("showSearchResults", false); appState.setGlobalState("stickyNavigation", false); appState.setGlobalState("showTray", false); + appState.setMenuState("currentNode", null); if (appState.getGlobalState("isTablet") === true) { appState.setGlobalState("showNavigation", false); @@ -660,10 +661,10 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo */ hideDialog: function (showMenu) { - setMode("default"); - - if(showMenu){ + if (showMenu) { this.showMenu(undefined, { skipDefault: true, node: appState.getMenuState("currentNode") }); + } else { + setMode("default"); } }, /** From cc52652481d77db4a1b42a8861744178e0c5602e Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 9 Oct 2018 09:44:44 +0200 Subject: [PATCH 054/585] Added some comments detailing the hack that is in place for the where clause. --- .../Repositories/Implement/DocumentRepository.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 6fc3653db3..d888f452ef 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -700,6 +700,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { Sql filterSql = null; + // Here we create a default where clause from a temp IContent which will look in the contentVersion table for the content name + // if we are searching in a list view that contains variants, we want to look in the contentVersionCultureVariation table instead. + // The resulting clause will be used in the foreach below to compare against the original clause that comes from the "filter" and if they are the same + // we know that we are searching a list view and the proper where clause will be replaced to look in contentVersionCultureVariation table for the names. var temp = Query().Where(x => x.Name.Contains("foo")); var clause = temp.GetWhereClauses().First().Item1.Split(' ')[0]; @@ -709,8 +713,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var filterClause in filter.GetWhereClauses()) { // fixme - is this the right way of doing it??? + + // var where = filterClause.Item1.Split(' ')[0] == clause - // normally, this would be the field alias of the coalesce result between ContentVersionCulture and NodeDto names, however + // normally, this would be the field alias (variantName) of the coalesce result between ContentVersionCulture and NodeDto names, however // you can't refer to field alias in a WHERE clause so we have to put the coalesce calculation instead which refers to the original field ? SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql : filterClause.Item1; From f63a9e93b0447d45003b911042de677356c41e77 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 9 Oct 2018 08:53:40 +0100 Subject: [PATCH 055/585] Merge remote-tracking branch 'origin/temp8' into temp8-3087-tree-grouping # Conflicts: # src/Umbraco.Web.UI/Umbraco/config/lang/en.xml # src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/config/lang/es.xml # src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml # src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml # src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml # src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml # src/Umbraco.Web/Trees/DictionaryTreeController.cs --- src/Umbraco.Web.UI/umbraco/config/lang/nb.xml | 1026 ------------- .../umbraco/config/lang/zh_tw.xml | 1346 ----------------- 2 files changed, 2372 deletions(-) delete mode 100644 src/Umbraco.Web.UI/umbraco/config/lang/nb.xml delete mode 100644 src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml deleted file mode 100644 index be99f6b561..0000000000 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ /dev/null @@ -1,1026 +0,0 @@ - - - - The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files - - - Angi domene - Revisjoner - Bla gjennom - Skift dokumenttype - Kopier - Opprett - Opprett pakke - Slett - Deaktiver - Tøm papirkurv - Eksporter dokumenttype - Importer dokumenttype - Importer pakke - Rediger i Canvas - Logg av - Flytt - Varslinger - Offentlig tilgang - Publiser - Avpubliser - Oppdater noder - Republiser hele siten - Gjenopprett - Rettigheter - Reverser - Send til publisering - Send til oversetting - Sorter - Oversett - Oppdater - - - Ingen tilgang. - Legg til domene - Fjern - Ugyldig node. - Ugyldig domeneformat. - Domene er allerede tilknyttet. - Språk - Domene - Domene '%0%' er nå opprettet og tilknyttet siden - Domenet '%0%' er nå slettet - Domenet '%0%' er allerede tilknyttet - Domenet '%0%' er nå oppdatert - eller rediger eksisterende domener -
Stier med ett nivå støttes, f.eks. "eksempel.com/no". Imidlertid bør det unngås. Bruk heller språkinnstillingen over.]]>
- Arv - Språk - Vil også gjelde denne noden, med mindre et underordnet domene også gjelder.]]> - Domener - - - Viser for - - - Velg - Velg gjeldende mappe - Gjør noe annet - Fet - Reduser innrykk - Sett inn skjemafelt - Sett inn grafisk overskrift - Rediger HTML - Øk innrykk - Kursiv - Midtstill - Juster tekst venstre - Juster tekst høyre - Sett inn lenke - Sett inn lokal lenke (anker) - Punktmerking - Nummerering - Sett inn makro - Sett inn bilde - Rediger relasjoner - Tilbake til listen - Lagre - Lagre og publiser - Lagre og planlegge - Lagre og send til publisering - Forhåndsvis - Forhåndsvisning er deaktivert siden det ikke er angitt noen mal - Velg formattering - Vis stiler - Sett inn tabell - - - For å endre det valge innholdets dokumenttype, velger du først en ny dokumenttype som er gyldig på gjeldende plassering. - Kontroller deretter at alle egenskaper blir overført riktig til den nye dokumenttypen og klikk på Lagre. - Innholdet har blitt republisert. - Nåværende egenskap - Nåværende type - Du kan ikke endre dokumenttype, ettersom det ikke er andre gyldige dokumenttyper på denne plasseringen. - Dokumenttype endret - Overfør egenskaper - Overfør til egenskap - Ny mal - Ny type - ingen - Innhold - Velg ny dokumenttype - Dokumenttypen på det valgte innhold ble endret til [new type], og følgende egenskaper ble overført: - til - Overføringen av egenskaper kunne ikke fullføres da en eller flere egenskaper er satt til å bli overført mer enn en gang. - Kun andre dokumenttyper som er gyldige for denne plasseringen vises. - - - Publisert - Om siden - Alias - (hvordan du ville beskrevet bildet over telefon) - Alternative lenker - Klikk for å redigere denne noden - Opprettet av - Opprinnelig forfatter - Oppdatert av - Opprettet den - Tidspunkt for opprettelse - Dokumenttype - Redigerer - Utløpsdato - Denne noden er endret siden siste publisering - Denne noden er enda ikke publisert - Sist publisert - Det er ingen elementer å vise i listen. - Mediatype - Link til media - Medlemsgruppe - Rolle - Medlemstype - Ingen dato valgt - Sidetittel - Egenskaper - Dette dokumentet er publisert, men ikke synlig ettersom den overliggende siden '%0%' ikke er publisert - Intern feil: dokumentet er publisert men finnes ikke i hurtigbuffer - Publisert - Publiseringsstatus - Publiseringsdato - Dato for avpublisering - Fjern dato - Sorteringsrekkefølgen er oppdatert - Trekk og slipp nodene eller klikk på kolonneoverskriftene for å sortere. Du kan velge flere noder ved å holde shift eller control tastene mens du velger. - Statistikk - Tittel (valgfri) - Alternativ tekst (valgfri) - Type - Avpubliser - Sist endret - Tidspunkt for siste endring - Fjern fil - Lenke til dokument - Medlem av gruppe(ne) - Ikke medlem av gruppe(ne) - Undersider - Åpne i vindu - - - Klikk for å laste opp - Slipp filene her... - - - Opprett et nytt medlem - Alle medlemmer - - - Hvor ønsker du å oprette den nye %0% - Opprett under - Velg en type og skriv en tittel - "dokumenttyper".]]> - "mediatyper".]]> - - - Til ditt nettsted - - Skjul - Hvis Umbraco ikke starter, kan det skyldes at pop-up vinduer ikke er tillatt - er åpnet i nytt vindu - Omstart - Besøk - Velkommen - - - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes - - - Done - - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items - - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items - - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items - - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items - - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items - - - Navn på lokal link - Rediger domener - Lukk dette vinduet - Er du sikker på at du vil slette - Er du sikker på at du vil deaktivere - Vennligst kryss av i denne boksen for å bekrefte sletting av %0% element(er) - Er du sikker på at du vil forlate Umbraco? - Er du sikker? - Klipp ut - Rediger ordboksnøkkel - Rediger språk - Sett inn lokal link - Sett inn spesialtegn - Sett inn grafisk overskrift - Sett inn bilde - Sett inn lenke - Sett inn makro - Sett inn tabell - Sist redigert - Lenke - Intern link: - Ved lokal link, sett inn "#" foran link - Åpne i nytt vindu? - Makroinnstillinger - Denne makroen har ingen egenskaper du kan endre - Lim inn - Endre rettigheter for - Innholdet i papirkurven blir nå slettet. Vennligst ikke lukk dette vinduet mens denne operasjonen foregår - Papirkurven er nå tom - Når elementer blir slettet fra papirkurven vil de være slettet for alltid - regexlib.com tjenesten opplever for tiden problemer som vi ikke har kontroll over. Vi beklager denne ubeleiligheten.]]> - Søk etter et regulært uttrykk for å legge inn validering til et felt. Eksempel: 'email, 'zip-code' 'url' - Fjern makro - Obligatorisk - Nettstedet er indeksert - Hurtigbufferen er blitt oppdatert. Alt publisert innhold er nå à jour. Alt upublisert innhold er fortsatt ikke publisert. - Hurtigbufferen for siden vil bli oppdatert. Alt publisert innhold vil bli oppdatert, mens upublisert innhold vil forbli upublisert. - Antall kolonner - Antall rader - Sett en plassholder-ID
Ved å sette en ID på plassholderen kan du legge inn innhold i denne malen fra underliggende maler, ved å referere denne ID'en ved hjelp av et <asp:content /> element.]]>
- Velg en plassholder ID fra listen under. Du kan bare velge ID'er fra den gjeldende malens overordnede mal.]]> - Klikk på bildet for å se det i full størrelse - Velg punkt - Se buffret node - - - %0%' under.
Du kan legge til flere språk under 'språk' i menyen til venstre.]]>
- Språk - - - Skriv inn ditt brukernavn - Skriv inn ditt passord - Navngi %0%... - Skriv inn navn... - Søk... - Filtrer... - Skriv inn nøkkelord (trykk på Enter etter hvert nøkkelord)... - - - Tillat på rotnivå - Kun dokumenttyper med denne innstillingen aktivert kan opprettes på rotnivå under Innhold og Mediearkiv - Tillatte underordnede noder - Sammensetting av dokumenttyper - Opprett - Slett arkfane - Beskrivelse - Ny arkfane - Arkfane - Miniatyrbilde - Aktiver listevisning - Viser undersider i en søkbar liste, undersider vises ikke i innholdstreet - Gjeldende listevisning - Den aktive listevisningsdatatypen - Opprett brukerdefinert listevisning - Fjern brukerdefinert listevisning - - - Legg til forhåndsverdi - Database datatype - Kontrollelement GUID - Kontrollelement - Knapper - Aktiver avanserte instillinger for - Aktiver kontektsmeny - Maksimum standard størrelse på innsatte bilder - Beslektede stilark - Vis etikett - Bredde og høyde - - - Dine data har blitt lagret, men før du kan publisere denne siden må du rette noen feil: - Den gjeldende Membership Provider støtter ikke endring av passord. (EnablePasswordRetrieval må være satt til sann) - %0% finnes allerede - Det var feil i dokumentet: - Det var feil i skjemaet: - Passordet bør være minst %0% tegn og inneholde minst %1% numeriske tegn - %0% må være et heltall - %0% under %1% er obligatorisk - %0% er obligatorisk - %0% under %1% er ikke i et korrekt format - %0% er ikke i et korrekt format - - - Filtypen er deaktivert av administrator - NB! Selv om CodeMirror er aktivert i konfigurasjon er det deaktivert i Internet Explorer pga. ustabilitet. - Fyll ut både alias og navn på den nye egenskapstypen! - Det er et problem med lese/skrive rettighetene til en fil eller mappe - Tittel mangler - Type mangler - Du er i ferd med å gjøre bildet større enn originalen. Det vil forringe kvaliteten på bildet, ønsker du å fortsette? - Feil i python-skriptet - Python-skriptet ble ikke lagret fordi det inneholder en eller flere feil - Startnode er slettet. Kontakt din administrator - Du må markere innhold før du kan endre stil - Det er ingen aktive stiler eller formateringer på denne siden - Sett markøren til venstre i de 2 cellene du ønsker å slå sammen - Du kan ikke dele en celle som allerede er delt. - Det er et problem dem datatypen som brukes til denne egenskapen. Kontroller innstillingene og prøv igjen. - - - Om - Handling - Muligheter - Legg til - Alias - Er du sikker? - Ramme - av - Avbryt - Cellemargin - Velg - Lukk - Lukk vindu - Kommentar - Bekreft - Behold proposjoner - Fortsett - Kopier - Opprett - Database - Dato - Standard - Slett - Slettet - Sletter... - Design - Dimensjoner - Ned - Last ned - Rediger - Endret - Elementer - E-post - Feil - Finn - Høyde - Hjelp - Ikon - Importer - Indre margin - Sett inn - Installer - Justering - Språk - Layout - Laster - Låst - Logg inn - Logg ut - Logg ut - Makro - Flytt - Mer - Navn - Ny - Neste - Nei - av - OK - Åpne - eller - Passord - Sti - Plassholder ID - Ett øyeblikk... - Forrige - Egenskaper - E-post som innholdet i skjemaet skal sendes til - Papirkurv - Gjenværende - Gi nytt navn - Forny - Påkrevd - Prøv igjen - Rettigheter - Søk - Server - Vis - Hvilken side skal vises etter at skjemaet er sendt - Størrelse - Sorter - Send - Type - Søk... - Opp - Oppdater - Oppgrader - Last opp - Url - Bruker - Brukernavn - Verdi - Visning - Velkommen... - Bredde - Ja - Mappe - Søkeresultater - Sorter - Avslutt sortering - Eksempel - Bytt passord - til - Listevisning - Lagrer... - nåværende - Innbygging - Hent - valgt - - - Bakgrunnsfarge - Fet - Tekstfarge - Skrifttype - Tekst - - - Side - - - Installasjonsprogrammet kan ikke koble til databasen - Kunne ikke lagre Web.Config-filen. Vennligst endre databasens tilkoblingsstreng manuelt. - Din database er funnet og identifisert som - Databasekonfigurasjon - installer-knappen for å installere Umbraco %0% databasen]]> - Neste for å fortsette.]]> - Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.

For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.

Klikk prøv på nytt når du er ferdig.
Mer informasjon om redigering av web.config her.

]]>
- Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator.]]> - Trykk på knappen oppgrader for å oppgradere databasen din til Umbraco %0%

Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå!

]]>
- Trykk Neste for å fortsette.]]> - neste for å fortsette konfigurasjonsveiviseren]]> - Passordet til standardbrukeren må endres!]]> - Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!

Ingen videre handling er nødvendig. Klikk neste for å fortsette.]]> - Passordet til standardbrukeren har blitt forandret etter installasjonen!

Ingen videre handling er nødvendig. Klikk Neste for å fortsette.]]> - Passordet er blitt endret! - Få en god start med våre introduksjonsvideoer - Ved å klikke på Neste-knappen (eller endre UmbracoConfigurationStatus i Web.config), godtar du lisensen for denne programvaren som angitt i boksen nedenfor. Legg merke til at denne Umbraco distribusjon består av to ulike lisenser, åpen kilde MIT lisens for rammen og Umbraco frivareverktøy lisens som dekker brukergrensesnittet. - Ikke installert. - Berørte filer og mapper - Mer informasjon om å sette opp rettigheter for Umbraco her - Du må gi ASP.NET brukeren rettigheter til å endre de følgende filer og mapper - Rettighetene er nesten perfekt satt opp!

Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]>
- Hvordan løse problemet - Klikk her for å lese tekstversjonen - innføringsvideo om å sette opp rettigheter for Umbraco eller les tekstversjonen.]]> - Rettighetsinnstillingene kan være et problem!

Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]>
- Rettighetsinstillingene er ikke klargjort for Umbraco!

For å kunne kjøre Umbraco, må du oppdatere rettighetsinnstillingene dine.]]>
- Rettighetsinnstillingene er perfekt!

Du er klar for å kjøre Umbraco og installere pakker!]]>
- Løser mappeproblem - Følg denne linken for mer informasjon om problemer med ASP.NET og oppretting av mapper - Konfigurerer mappetillatelser - - Jeg ønsker å starte fra bunnen. - lær hvordan) Du kan fortsatt velge å installere Runway senere. Vennligst gå til Utvikler-seksjonen og velg Pakker.]]> - Du har akkurat satt opp en ren Umbraco plattform. Hva vil du gjøre nå? - Runway er installert - Dette er vår liste av anbefalte moduler- Kryss av de du ønsker å installere, eller se denfulle listen av moduler ]]> - Bare anbefalt for erfarne brukere - Jeg vil starte med en enkel webside - "Runway" er en enkel webside som utstyrer deg med noen grunnleggende dokumenttyper og maler. Veiviseren kan sette opp Runway for deg automatisk, men du kan enkelt endre, utvide eller slette den. Runway er ikke nødvendig, og du kan enkelt bruke Umbraco uten den. Imidlertidig tilbyr Runway et enkelt fundament basert på de beste metodene for å hjelpe deg i gang fortere enn noensinne. Hvis du velger å installere Runway, kan du også velge blant grunnleggende byggeklosser kalt Runway Moduler for å forøke dine Runway-sider.

Sider inkludert i Runway: Hjemmeside, Komme-i-gang, Installere moduler.
Valgfrie Moduler: Toppnavigasjon, Sidekart, Kontakt, Galleri.
]]>
- Hva er Runway - Steg 1/5 Godta lisens - Steg 2/5 Database konfigurasjon - Steg 3/5: Valider filrettigheter - Steg 4/5: Skjekk Umbraco sikkerheten - Steg 5/5: Umbraco er klar for deg til å starte! - Tusen takk for at du valgte Umbraco! - Se ditt nye nettsted Du har installert Runway, hvorfor ikke se hvordan ditt nettsted ser ut.]]> - Mer hjelp og info Få hjelp fra vårt prisbelønte samfunn, bla gjennom dokumentasjonen eller se noen gratis videoer på hvordan man bygger et enkelt nettsted, hvordan bruke pakker og en rask guide til Umbraco terminologi]]> - Umbraco %0% er installert og klar til bruk - web.config filen, og oppdatere AppSetting-nøkkelen UmbracoConfigurationStatus til verdien '%0%']]> - starte øyeblikkelig ved å klikke på "Start Umbraco" knappen nedenfor.
Hvis du er ny på Umbraco, kan du finne mange ressurser på våre komme-i-gang sider.]]>
- Start Umbraco For å administrere din webside, åpne Umbraco og begynn å legge til innhold, oppdatere maler og stilark eller utvide funksjonaliteten]]> - Tilkobling til databasen mislyktes. - Umbraco Versjon 3 - Umbraco Versjon 4 - Se - Umbraco %0% for en ny installasjon eller oppgradering fra versjon 3.0.

Trykk "neste" for å starte veiviseren.]]>
- - - Språkkode - Språk - - - Du har vært inaktiv og vil logges ut automatisk om - Forny innlogging for å lagre - - - Da er det søndag! - Smil, det er mandag! - Hurra, det er tirsdag! - For en herlig onsdag! - Gledelig torsdag! - Endelig fredag! - Gledelig lørdag - Logg på nedenfor - Logg på med - Din sesjon er utløpt - © 2001 - %0%
umbraco.com

]]>
- - - Skrivebord - Seksjoner - Innhold - - - Velg side over... - %0% er nå kopiert til %1% - Kopier til - %0% er nå flyttet til %1% - Flytt til - har blitt valgt som rot til ditt nye innhold, klikk 'ok' nedenfor. - Ingen node er valgt, vennligst velg en node i listen over før du klikker 'fortsett' - Gjeldende nodes type tillates ikke under valgt node - Gjeldende node kan ikke legges under en underordnet node - Denne noden kan ikke ligge på rotnivå - Handlingen tillates ikke. Du mangler tilgang til en eller flere underordnede noder. - Relater kopierte elementer til original(e) - - - Rediger dine varsler for %0% - - Hei %0%

- -

Dette er en automatisk mail for å informere om at handlingen '%1%' - er blitt utført på siden '%2%' - av brukeren '%3%' -

- -

-

Rettelser:

- - %6% -
-

- - - -

Ha en fin dag!

- Vennlig hilsen Umbraco roboten -

]]>
- [%0%] Varsling om %1% utført på %2% - Varslinger - - - Umbraco-pakker har vanligvis endelsen ".umb" eller ".zip".]]> - Utvikler - Demonstrasjon - Dokumentasjon - Metadata - Pakkenavn - Pakken inneholder ingen elementer -
Du kan trygt fjerne pakken fra systemet ved å klikke "avinstaller pakke" nedenfor.]]>
- Ingen oppdateringer tilgjengelig - Alternativer for pakke - Lesmeg for pakke - Pakkebrønn - Bekreft avinstallering - Pakken ble avinstallert - Pakken ble vellykket avinstallert - Avinstaller pakke - Advarsel: alle dokumenter, media, etc. som som er avhengig av elementene du sletter, vil slutte å virke, noe som kan føre til ustabilitet, så avinstaller med forsiktighet. Hvis du er i tvil, kontakt pakkeutvikleren.]]> - Last ned oppdatering fra pakkeregisteret - Oppgrader pakke - Oppgraderingsinstrukser - Det er en oppdatering tilgjengelig for denne pakken. Du kan laste den ned direkte fra pakkebrønnen. - Pakkeversjon - Pakkeversjonshistorie - Se pakkens nettsted - - - Lim inn med full formattering (Anbefales ikke) - Teksten du er i ferd med å lime inn, inneholder spesialtegn eller formattering. Dette kan skyldes at du kopierer fra f.eks. Microsoft Word. Umbraco kan fjerne denne spesialformatteringen automatisk slik at innholdet er mer velegnet for visning på en webside. - Lim inn som ren tekst, dvs. fjern al formattering - Lim inn og fjern uegnet formatering (anbefalt) - - - Avansert: Beskytt ved å velge hvilke brukergrupper som har tilgang til siden - ved å bruke Umbraco's medlems-grupper]]> - Du må opprette en medlemsgruppe før du kan bruke rollebasert autentikasjon. - Feilside - Brukt når personer logger på, men ikke har tilgang - Hvordan vil du beskytte siden din? - %0% er nå beskyttet - Beskyttelse fjernet fra %0% - Innloggingsside - Velg siden som har loginformularet - Fjern beskyttelse - Velg sidene som inneholder login-skjema og feilmelding ved feil innolgging. - Velg rollene som har tilgang til denne siden - Sett brukernavn og passord for denne siden - Enkelt: Beskytt ved hjelp av brukernavn og passord - Om du ønsker å bruke enkel autentisering via ett enkelt brukernavn og passord - - - %0% kunne ikke publiseres fordi den har planlagt utgivelsesdato. - %0% ble ikke publisert. Ett eller flere felter ble ikke godkjent av validering. - %0% kunne ikke publiseres fordi et tredjepartstillegg avbrøt handlingen. - %0% kan ikke publiseres fordi en overordnet side ikke er publisert. - Inkluder upubliserte undersider - Publiserer - vennligst vent... - %0% av %1% sider har blitt publisert... - %0% er nå publisert - %0% og alle undersider er nå publisert - Publiser alle undersider - ok for å publisere %0% og dermed gjøre innholdet synlig for alle.

Du kan publisere denne siden og alle dens undersider ved å krysse av Publiser alle undersider nedenfor.]]>
- - - Du har ikke konfigurert noen godkjente farger - - - skriv inn ekstern lenke - velg en intern side - Tittel - Lenke - Åpne i nytt vindu - Skriv inn en tekst - Skriv inn en lenke - - - Nullstill - - - Gjeldende versjon - Rød tekst vil ikke bli vist i den valgte versjonen. , grønn betyr lagt til]]> - Dokumentet er tilbakeført til en tidligere versjon - Dette viser den valgte versjonen som HTML, bruk avviksvisningen hvis du ønsker å se forksjellene mellom to versjoner samtidig. - Tilbakefør til - Velg versjon - Vis - - - Rediger scriptfilen - - - Concierge - Innhold - Courier - Utvikler - Umbraco konfigurasjonsveiviser - Mediaarkiv - Medlemmer - Nyhetsbrev - Innstillinger - Statistikk - Oversettelse - Brukere - Hjelp - Skjemaer - Analytics - - - gå til - Hjelpeemner for - Videokapitler for - De beste Umbraco opplæringsvideoer - - - Standardmal - Ordboksnøkkel - For å importere en dokumenttype, finn ".udt" filen på datamaskinen din ved å klikke "Utforsk" knappen og klikk "Importer" (du vil bli spurt om bekreftelse i det neste skjermbildet) - Ny tittel på arkfane - Nodetype - Type - Stilark - Script - Stilark-egenskap - Arkfane - Tittel på arkfane - Arkfaner - Hovedinnholdstype aktivert - Denne dokumenttypen bruker - som hoveddokumenttype. Arkfaner fra hoveddokumenttyper vises ikke og kan kun endres på hoveddokumenttypen selv. - Ingen egenskaper definert i denne arkfanen. Klikk på "legg til ny egenskap" lenken i toppen for å opprette en ny egenskap. - Hovedinnholdstype - Opprett tilhørende mal - - - Sort order - Creation date - Sortering ferdig. - Dra elementene opp eller ned for å arrangere dem. Du kan også klikke kolonneoverskriftene for å sortere alt på en gang. - - - - En feil oppsto - Utilstrekkelige brukertillatelser, kunne ikke fullføre operasjonen - Avbrutt - Handlingen ble avbrutt av et tredjepartstillegg - Publisering ble avbrutt av et tredjepartstillegg - Egenskaptypen finnes allerede - Egenskapstype opprettet - DataType: %1%]]> - Egenskapstype slettet - Innholdstype lagret - Du har opprettet en arkfane - Arkfane slettet - Arkfane med id: %0% slettet - Stilarket ble ikke lagret - Stilarket ble lagret - Stilark lagret uten feil - Datatype lagret - Ordbokelement lagret - Publiseringen feilet fordi den overliggende siden ikke er publisert - Innhold publisert - og er nå synlig for besøkende - Innhold lagret - Husk å publisere for å gjøre endringene synlig for besøkende - Sendt for godkjenning - Endringer har blitt sendt til godkjenning - Media lagret - Media lagret uten feil - Medlem lagret - Stilarksegenskap lagret - Stilark lagret - Mal lagret - Feil ved lagring av bruker (sjekk loggen) - Bruker lagret - Brukertypen lagret - Filen ble ikke lagret - Filen kunne ikke lagres. Vennligst sjekk filrettigheter - Filen ble lagret - Filen ble lagret uten feil - Språk lagret - Python-skriptet ble ikke lagret - Python-skriptet kunne ikke lagres fordi det inneholder en eller flere feil - Python-skriptet er lagret! - Ingen feil i python-skriptet! - Malen ble ikke lagret - Vennligst forviss deg om at du ikke har to maler med samme alias - Malen ble lagret - Malen ble lagret uten feil! - Innhold avpublisert - Delmal lagret - Delmal lagret uten feil - Delmal ble ikke lagret! - En feil oppsto ved lagring av delmal - Script visning lagret - Script visning lagret uten feil! - Script visning ikke lagret - En feil oppsto under lagring av filen. - En feil oppsto under lagring av filen. - - - Bruk CSS syntaks f.eks: h1, .redHeader, .blueText - Rediger stilark - Rediger egenskap for stilark - Navn for å identifisere stilarksegenskapen i rik-tekst editoren - Forhåndsvis - Stiler - - - Rediger mal - Sett inn innholdsområde - Sett inn plassholder for innholdsområde - Sett inn ordbokselement - Sett inn makro - Sett inn Umbraco sidefelt - Hovedmal - Hurtigguide til Umbraco sine maltagger - Mal - - - Rich Text Editor - Image - Macro - Embed - Headline - Quote - Sett inn element - Velg layout - Legg til rad - Legg til innhold - Slipp innhold - Raden har tilpasset design - - Innholdstypen er ikke tillatt her - Innholdstypen er tillatt her - - Klikk for å bygge inn - Klikk for å sette inn et bilde - Bildetekst... - Skriv her... - - Rutenettoppsett - Et oppsett er det overordnede arbeidsområdet til ditt rutenett - du vil typisk kun behøve ett eller to - Legg til rutenettoppsett - Juster oppsettet ved å konfigurere kolonnebredder og legge til ytterligere seksjoner - Radkonfigurasjoner - Rader er forhåndsdefinerte celler arrangert vannrett - Legg til radkonfigurasjon - Juster raden ved å sette cellebredder og legge til flere celler - - Kolonner - Totalt antall kolonner i rutenettet - - Innstillinger - Konfigurer hvilke innstillinger brukeren kan endre - - Stiler - Konfigurer hvilke stiler redaktørene kan endre - - Innstillingene lagres kun når konfigurasjonen er gyldig - - Tillatt alle editorer - Tillat alle radkonfigurasjoner - Bruk som standard - Velg ekstra - Velg standard - er lagt til - - - Alternativt felt - Alternativ tekst - Store/små bokstaver - Encoding - Felt som skal settes inn - Konverter linjeskift - Erstatter et linjeskift med htmltaggen <br> - Egendefinerte felt - Ja, kun dato - Formatter som dato - HTML koding - Formater spesialtegn med tilsvarende HTML-tegn. - Denne teksten vil settes inn etter verdien av feltet - Denne teksten vil settes inn før verdien av feltet - Små bokstaver - Ingen - Sett inn etter felt - Sett inn før felt - Rekursivt - Standardfelter - Store bokstaver - URL koding - Dersom innholdet av feltene skal sendes til en URL skal spesialtegn formatteres - Denne teksten vil benyttes dersom feltene over er tomme - Dette feltet vil benyttes dersom feltet over er tomt - Ja, med klokkeslett. Dato/tid separator: - - - Oppgaver satt til deg - som du er tildelt. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML".
For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]>
- Lukk oppgave - Oversettelses detaljer - Last ned all oversettelsesoppgaver som XML - Last ned XML - Last ned XML DTD - Felt - Inkluder undersider - - [%0%] Oversettingsoppgave for %1% - Ingen oversettelses-bruker funnet. Vennligst opprett en oversettelses-bruker før du begynner å sende innhold til oversetting - Oppgaver opprettet av deg - opprettet av deg. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]> - Siden '%0%' har blitt sendt til oversetting - Send til oversetting - Tildelt av - Oppgave åpnet - Antall ord - Oversett til - Oversetting fullført. - Du kan forhåndsvise sidene du nettopp har oversatt ved å klikke nedenfor. Hvis den originale siden finnes, vil du få en sammenligning av sidene. - Oversetting mislykkes, XML filen kan være korrupt - Alternativer for oversetting - Oversetter - Last opp XML med oversettelse - - - Hurtigbufferleser - Papirkurv - Opprettede pakker - Datatyper - Ordbok - Installerte pakker - Installer utseende - Installer startpakke - Språk - Installer lokal pakke - Makroer - Mediatyper - Medlemmer - Medlemsgrupper - Roller - Medlemstyper - Dokumenttyper - Pakker - Pakker - Python Filer - Installer fra pakkeregister - Installer Runway - Runway moduler - Skriptfiler - Skript - Stiler - Maler - Analytics - Brukertillatelser - Brukertyper typer - Brukere - - - - - - Ny oppdatering er klar - %0% er klar, klikk her for å laste ned - Ingen forbindelse til server - Kunne ikke sjekke etter ny oppdatering. Se trace for mere info. - - - Administrator - Kategorifelt - Bytt passord - Nytt passord - Bekreft nytt passord - Du kan endre passordet til Umbraco ved å fylle ut skjemaet under og klikke "Bytt passord" knappen. - Innholdskanal - Beskrivelsesfelt - Deaktiver bruker - Dokumenttype - Redaktør - Utdragsfelt - Språk - Brukernavn - Øverste nivå i Media - Moduler - Deaktiver tilgang til Umbraco - Passord - Nullstill passord - Passordet er endret - Bekreft nytt passord - Nytt passord - Nytt passord kan ikke være blankt - Gjeldende passord - Feil passord - Nytt og bekreftet passord må være like - Nytt og bekreftet passord må være like - Overskriv tillatelser på undernoder - Du redigerer for øyeblikket tillatelser for sidene: - Velg sider for å redigere deres tillatelser - Søk i alle undersider - Startnode - Navn - Brukertillatelser - Forfatter - Oversetter - Endre - Din profil - Din historikk - Sesjonen utløper om - -
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml deleted file mode 100644 index 93f6f5a10f..0000000000 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ /dev/null @@ -1,1346 +0,0 @@ - - - - The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files - - - 管理主機名稱 - 跟蹤審計 - 流覽節點 - 改變文檔類型 - 複製 - 創建 - 創建擴展包 - 刪除 - 禁用 - 清空回收站 - 匯出文檔類型 - 導入文檔類型 - 導入擴展包 - 即時編輯模式 - 退出 - 移動 - 提醒 - 公眾存取權限 - 發佈 - 取消發佈 - 重新載入節點 - 重新發佈整站 - 回復 - 許可權 - 回滾 - 提交至發佈者 - 發送給翻譯 - 排序 - 提交至發佈者 - 翻譯 - 更新 - 預設值 - - - 禁止訪問 - 添加功能變數名稱 - 移除 - 錯誤的節點 - 功能變數名稱錯誤 - 功能變數名稱重複 - 語言 - 功能變數名稱 - 新功能變數名稱 '%0%' 已創建 - 功能變數名稱 '%0%' 已刪除 - 功能變數名稱 '%0%' 已使用 - 功能變數名稱 '%0%' 已更新 - 編輯當前功能變數名稱 - - 繼承 - 語言 - 或從父節點繼承文化設定。
- 也會改變目前節點設定,除非下方網域有其他項目。]]>
- 功能變數名稱 - - - 查看 - - - 清除選擇 - 選擇 - 選擇目前資料夾 - 做別的事情 - 粗體 - 取消段落縮進 - 插入表單字段 - 插入圖片標題 - 編輯Html - 段落縮進 - 斜體 - 居中 - 左對齊 - 右對齊 - 插入連結 - 插入本地連結(錨點) - 圓點列表 - 數字清單 - 插入巨集 - 插入圖片 - 編輯關聯 - 回到清單 - 保存 - 保存並發佈 - 保存並提交審核 - 保存清單檢視 - 預覽 - 因未設置範本無法預覽 - 選擇樣式 - 顯示樣式 - 插入表格 - 產生模組 - - - 要更改所選節點的文檔類型,先在列表中選擇合適的文檔類型。 - 然後設置當前文檔類型到新文檔類型的各欄位間的對應映射關係並保存。 - 內容已被重新發佈 - 當前屬性 - 當前類型 - 不能改變文檔類型,因為沒有可替代的類型。 - 文檔類型已更改 - 要映射的欄位 - 映射欄位 - 新範本 - 新類型 - - 內容 - 選擇新的文檔類型 - 選中文檔的類型已被成功更改為[new type],以下欄位被映射: - - 不能完成欄位映射,因為存在一個欄位映射至多欄位的問題。 - 僅顯示可作為替代的文檔類型。 - - - 已發表 - 關於本頁 - 別名 - (圖片的替代文本) - 替代連結 - 點擊編輯 - 創建者 - 創建者 - 更新者 - 創建時間 - 此文件創建的日期時間 - 文檔類型 - 編輯 - 過期於 - 該項發佈之後有更改 - 該項沒有發佈 - 最近發佈 - 沒有可供顯示的項目 - 此列表中沒有可供顯示的項目 - 媒體類型 - 媒體連結位址 - 會員組 - 角色 - 會員類型 - 沒有選擇時間 - 頁標題 - 屬性 - 該文檔不可見,因為其上級 '%0%' 未發佈。 - 糟糕:該文檔已發佈,但是沒有更新至緩存(內部錯誤) - 糟糕:沒辦法連結到此網址(內部錯誤-請參見記錄) - 糟糕:此文件已經發表,但是網址和其他內容相衝 %0% - 發佈 - 發佈狀態 - 發佈於 - 取消發表於 - 清空時間 - 排序完成 - 拖拽項目或按一下列頭即可排序,可以按住Shift多選。 - 統計 - 標題(可選) - 其他說明文字(可選) - 類型 - 取消發佈 - 最近編輯 - 本文件修改時間 - 移除文件 - 連結到文檔 - 會員組成員 - 非會員組成員 - 子項目 - 目標 - 預計發表的時間(伺服器端) - 這是什麼意思?]]> - - - 點選以便上傳 - 拖曳檔案至此... - 媒體連結 - 或按這裡選擇檔案 - 只允許檔案類型為 - 檔案大小上限為 - - - 新增一位會員 - 所有會員 - - - 您想在哪裡創建 %0% - 創建在 - 選擇類型和標題 - "文檔類型"處變更。]]> - "媒體類型"處變更。]]> - 文檔類型沒有相關範本 - 沒有資料夾 - 新資料類別 - - - 流覽您的網站 - - 隱藏 - 如果Umbraco沒有打開,您可能需要允許彈出式視窗。 - 已經在新視窗中打開 - 重啟 - 訪問 - 歡迎 - - - 留下 - 放棄變更 - 您有未存檔的變更 - 您確定要離開本頁? - 您有未存檔的變更 - - - 完成 - 刪除 %0% 個項目 - 刪除 %0% 個項目 - 刪除 %1% 個中的 %0% 個項目 - 刪除 %1% 個中的 %0% 個項目 - 已發佈 %0% 個項目 - 已發佈 %0% 個項目 - 已發佈 %1% 個中的 %0% 個項目 - 已發佈 %1% 個中的 %0% 個項目 - 取消發佈 %0% 個項目 - 取消發佈 %0% 個項目 - 取消發佈 %1 個中的 %0% 個項目 - 取消發佈 %1 個中的 %0% 個項目 - 移動 %0% 個項目 - 移動 %0% 個項目 - 移動 %1 個中的 %0% 個項目 - 移動 %1 個中的 %0% 個項目 - 複製 %0% 個項目 - 複製 %0% 個項目 - 複製 %1 個中的 %0% 個項目 - 複製 %1 個中的 %0% 個項目 - - - 錨點名稱 - 管理主機名稱 - 關閉窗口 - 您確定要刪除嗎 - 您確定要禁用嗎 - 按一下此框確定刪除%0%項 - 您確定嗎? - 您確定嗎? - 剪切 - 編輯字典項 - 編輯語言 - 插入本地連結 - 插入字元 - 插入圖片標題 - 插入圖片 - 插入連結 - 插入巨集 - 插入表格 - 最近編輯 - 連結 - 內部連結: - 本地連結請用“#”號開頭 - 在新視窗中打開? - 巨集設置 - 本巨集沒有包含您可以編輯的屬性 - 粘貼 - 編輯許可權 - 正在清空回收站,請不要關閉窗口。 - 回收站已清空 - 從回收站刪除的項目將不可恢復 - regexlib.com的網站服務目前出現些狀況,而我們無能為力。我們對此不便感到十分抱歉。]]> - 查找規則運算式來驗證輸入,如: 'email、'zip-code'、'url'。 - 移除巨集 - 必填項目 - 網站已重建索引 - 網站緩存已刷新,所有已發佈的內容更新生效。 - 網站緩存將會刷新,所有已發佈的內容將會更新。 - 表格列數 - 表格行數 - 設定預留位置代碼 以便您要在子範本中插入內容到本範本時,填入此代碼到 <asp:content /> 裡面。]]> - 選擇預留位置代碼 於此清單中。您只能選擇目前父範本中的代碼。]]> - 點擊圖片查看完整大小 - 拾取項 - 查看緩存項 - 新增資料夾... - 與原本相關 - 最友善的社群 - 頁面連結 - 打開此連結文檔至新視窗或標籤頁 - 打開此連結文檔至全新視窗 - 打開此連結文檔在原本視窗中 - 媒體連結 - 選擇媒體 - 選擇圖示 - 選擇項目 - 選擇連結 - 選擇巨集 - 選擇內容 - 選擇會員 - 選擇會員群組 - 沒有找到任何圖示 - 本巨集沒有需要參數 - 外部登入提供者 - 例外細節 - 詳細記錄 - 內部例外 - 連結您的 - 取消連結您的 - 帳戶 - 選擇編輯器 - - - %0%' 編輯不同語言版本,
您可以在左方選單「語言」中增添新的語言 - ]]>
- 語言名稱 - - - 輸入您的使用者名稱 - 輸入您的密碼 - 確認您的密碼 - 命名此 %0%... - 輸入一個名稱 - 標籤... - 輸入一段描述... - 搜尋請輸入... - 過濾請輸入... - 增加標籤(每個標籤後請按輸入鍵)... - 輸入您的電子郵件 - - - 允許放置於根節點 - 只有勾選「允許放置於根節點」的內容種類可以放在內容樹或媒體樹的最頂端 - 允許子項節點類型 - 文檔種類集合 - 創建 - 刪除選項卡 - 描述 - 新建選項卡 - 選項卡 - 縮略圖 - 允許清單檢視 - 允許內容項目顯示成可以排列及搜尋的清單,子項目不會被顯示 - 目前清單檢視 - 作用中的清單檢視資料類別 - 新增自訂清單檢視 - 移除自訂清單檢視 - - - 添加預設值 - 資料庫資料類型 - 資料類型唯一標識 - 渲染控制項 - 按鈕 - 允許高級設置 - 允許快顯功能表 - 插入圖片預設最大 - 關聯的樣式表 - 顯示標籤 - 寬和高 - - - 資料已保存,但是發佈前您需要修正一些錯誤: - 當前成員提供程式不支援修改密碼(EnablePasswordRetrieval的值應該為true) - %0% 已存在 - 發現錯誤: - 發現錯誤: - 密碼最少%0%位元,且至少包含%1%位元非字母數位記號 - %0% 必須是整數 - %1% 中的 %0% 欄位是必填項 - %0% 是必填項 - %1% 中的 %0% 格式不正確 - %0% 格式不正確 - - - 收到伺服器傳來的錯誤 - 該檔案類型已被管理員禁用 - 注意,儘管配置中允許CodeMirror,但是它在IE上不夠穩定,所以無法在IE運行。 - 請為新的屬性類型填寫名稱和別名! - 許可權有問題,訪問指定文檔或資料夾失敗! - 讀取片段視圖腳本錯誤(檔案:%0%) - 讀取使用者控制項 %0% 錯誤 - 讀取使用者控制項 %0% 錯誤(組件:%0%,類別:%1%) - 讀取巨集引擎腳本錯誤(檔案:%0%) - 請輸入標題 - 請選擇類型 - 圖片尺寸大於原始尺寸不會提高圖片品質,您確定要把圖片尺寸變大嗎? - python腳本錯誤 - python腳本未保存,因為包含錯誤。 - 預設打開頁面不存在,請聯繫管理員 - 請先選擇內容,再設置樣式。 - 沒有可用的樣式 - 請把游標放在您要合併的兩個儲存格中的左邊儲存格 - 非合併儲存格不能分離。 - 這是此屬性所使用的資料類別設定錯誤,請檢查資料類別 - - - 關於 - 操作 - 操作 - 添加 - 別名 - 所有 - 您確定嗎? - 回去 - 邊框 - - 取消 - 儲存格邊距 - 選擇 - 關閉 - 關閉窗口 - 備註 - 確認 - 強制屬性 - 繼續 - 複製 - 創建 - 資料庫 - 時間 - 默認 - 刪除 - 已刪除 - 正在刪除… - 設計 - 規格 - - 下載 - 編輯 - 已編輯 - 元素 - 郵箱 - 錯誤 - 查找文檔 - - 幫助 - 圖示 - 導入 - 內邊距 - 插入 - 安裝 - 不合格 - 對齊 - 語言 - 佈局 - 載入中 - 鎖定 - 登入 - 退出 - 登出 - 巨集 - 必要 - 移動 - 更多 - 名稱 - 新的 - 下一步 - - 屬於 - 確定 - 打開 - - 密碼 - 路徑 - 預留位置代碼 - 請稍候… - 上一步 - 屬性 - 接收資料郵箱 - 回收站 - 保持狀態中 - 重命名 - 更新 - 必要 - 重試 - 許可權 - 搜索 - 伺服器 - 顯示 - 在發送時預覽 - 大小 - 排序 - 送出 - 類型 - 輸入內容開始搜尋… - - 更新 - 更新 - 上傳 - 連結位址 - 用戶 - 用戶名 - - 查看 - 歡迎… - - - 資料夾 - 搜尋結果 - 重新排列 - 我已經完成排列 - 預覽 - 更改密碼 - - 清單檢視 - 存檔中... - 目前 - 內嵌 - 選取的 - - - - - - - - - - - 增加標籤頁 - 增加屬性 - 增加編輯器 - 增加範本 - 增加子節點 - 增加子項目 - 編輯資料類別 - 瀏覽區塊 - 捷徑 - 顯示捷徑 - 開關清單檢視 - 開關是否允許為根項目 - - - 背景色 - 粗體 - 前景色 - 字體 - 文本 - - - 頁面 - - - 無法連接到資料庫。 - 無法保存web.config檔,請手工修改。 - 發現資料庫 - 資料庫配置 - 安裝 按鈕來安裝Umbraco資料庫 %0% - ]]> - 下一步繼續。]]> - 沒有找到資料庫!請確認檔案"web.config"中的字串"connection string"是否正確。

-

請編輯檔案"web.config" (例如使用Visual Studio或您喜歡的編輯器),移動到檔案底部,並在名稱為"UmbracoDbDSN"的字串中設定資料庫連結資訊,並存檔。

-

- 點選重試按鈕當上述步驟完成。
- - 在此查詢更多編輯web.config的資訊。

]]>
- - 若需要時,請聯繫您的網路公司。如果您在本地機器或伺服器安裝的話,您也許需要聯絡系統管理者。]]> - - 點選升級按鈕來升級Umbraco資料庫 %0%

-

- 請別擔心 - 不會刪除任何資料而且馬上就會繼續運作! -

- ]]>
- 點選下一步繼續。]]> - 下一步繼續設定精靈。]]> - 預設使用者的密碼必須更改!]]> - 預設使用者已經被暫停或沒有Umbraco的使用權!

不需更多的操作步驟。點選下一步繼續。]]> - 安裝後預設使用者的密碼已經成功修改!

不需更多的操作步驟。點選下一步繼續。]]> - 密碼已更改 - 作為入門者,從視頻教程開始吧! - 點擊下一步 (或在Web.config中自行修改UmbracoConfigurationStatus),意味著您接受上述授權合約。 - 安裝失敗。 - 受影響的檔和資料夾 - 此處查看更多資訊 - 您需要對以下檔和資料夾授於ASP.NET用戶修改許可權 - 您的權限設定幾近完美!

- 您可以正常執行Umbraco沒有任何問題,只差您將沒有辦法安裝那些建議需要全部許可權的插件。]]>
- 如何解決 - 點擊閱讀文字版 - 影片教學來瞭解如何設定Umbraco的資料夾權限或閱讀文字版本。]]> - 您的權限可能有點小問題! -

- 您可以正常執行Umbraco沒有任何問題,然而您將無法新增資料夾或安裝那些可以讓Umbraco發揮全力的插件。]]>
- 您的權限設定尚未未完成! -

- 您需要更新權限設定才能執行Umbraco。]]>
- 您的權限設定完美無瑕!

- 您已經準備好執行Umbraco和安裝插件!]]>
- 解決資料夾問題 - 點此查看ASP.NET和創建資料夾的問題解決方案 - 設置資料夾許可權 - - 我要從頭開始 - 學習該怎麼做) - 您晚點仍可以選擇安裝Runway,請至開發者區域選擇安裝。 - ]]> - 您剛剛安裝了一個乾淨的系統,要繼續嗎? - “Runway”已安裝 - - 這是我們的模組推薦清單,選取您想要安裝的項目,或者至 查詢完整清單。 - ]]> - 僅推薦高級用戶使用 - 給我一個簡單的網站 - - "Runway"是一個提供基本檔案類別和範本的簡單網站。安裝程式會自動幫您設定Runway, - 但你仍可輕易編輯,擴充或移除它。它並非必要項目而且您可以在沒它的情況下完美執行Umbraco。然而, - Runway提供一個輕鬆簡便但基於寶貴經驗的平台讓您可以更快開始。 - 如果您安裝Runway,您還可以選擇名為「Runway模組」的基本區塊來加強Runway頁面。 -

- - 內含於Runway: 首頁,準備開始頁面,模組安裝頁面。
- 可選模組: 上方瀏覽列,網站地圖,聯絡,藝廊。 -
- ]]>
- “Runway”是什麼? - 步驟 1/5:接受授權合約 - 步驟 2/5:資料庫配置 - 步驟 3/5:文件許可權驗證 - 步驟 4/5:系統安全性 - 步驟 5/5:一切就緒,可以開始使用系統。 - 感謝選擇我們的產品 - 參觀您的新網站 -您剛安裝好Runway,何不瞧瞧它的模樣。]]> - 更多的幫忙與資訊 -從我們獲獎的社群得到幫助,瀏覽文件,或觀看免費影片來瞭解如何輕鬆架設網站,如何使用插件,和瞭解Umbraco項目名稱的快速上手指引。]]> - 系統 %0% 安裝完畢 - /web.config 檔案並且更新AppSetting中的字串UmbracoConfigurationStatus 內容為 '%0%'。]]> - 快速開始指引。
如果您是Umbraco的新成員, -您可以在其中找到相當多的資源。]]>
- 啟動Umbraco -想要管理您的網站時,只需開啟Umbraco後台便可增加內容,更新範本和樣式表,或增添新功能。]]> - 無法連接到資料庫。 - 系統版本 3 - 系統版本 4 - 觀看 -
- 點選"下一步"來啟動精靈。]]>
- - - 語言代碼 - 語言名稱 - - - 使用者在空閒狀態下將會自動登出 - 已更新,繼續工作。 - - - 超級星期天快樂 - 瘋狂星期一快樂 - 熱鬧星期二快樂 - 美妙星期三快樂 - 悅耳星期四快樂 - 時髦星期五快樂 - 喵喵星期六快樂 - 下方登入 - 登入使用 - 連線時間過了 - © 2001 - %0%
Umbraco.com

]]>
- 忘記密碼? - 一封內有重設密碼連結的電子郵件已經寄出給您 - 一封內有重設密碼連結的電子郵件已經寄到此信箱 - 回到登入畫面 - 請輸入新密碼 - 您的密碼已經更新 - 您點選的連結是無效或過期的 - Umbraco:重設密碼 - 您登入到後台的使用者名稱是:%0%

點選這裡來重設您的密碼或將此連結複製/貼上到您的瀏覽器:

%1%

]]>
- - - 儀錶板 - 區域 - 內容 - - - 選擇上面的頁面… - %0% 被複製到 %1% - 將 %0% 複製到 - %0% 已被移動到 %1% - 將 %0% 移動到 - 作為內容的根結點,點“確定”。 - 尚未選擇節點,請選擇一個節點點擊“確定”。 - 類型不符不允許選擇 - 該項不能移到其子項 - 當前節點不能建在根節點下 - 您在子項的許可權不夠,不允許該操作。 - 複本和原本建立關聯 - - - 為 %0% 編寫通知 - - 哈嘍 %0%

- -

這是一封自動產生的信件來通知您 %1% 工作 - 已經在頁面 %2% 上由使用者 %3% 執行完成 -

- -

-

更新摘要:

- - %6% -
-

- - - -

祝您有美好的一天!

- Umbraco機器人 謹上 -

]]>
- 在 %2%,[%0%] 關於 %1% 的通告已執行。 - 通知 - - - - 按鈕並點選該檔案。Umbraco擴展包通常有「.zip」的副檔名。 - ]]> - 作者 - 演示 - 文檔 - 中繼資料 - 名稱 - 擴展包不含任何項 -
- 您可以點選下方「移除擴展包」來安全地移除此項目。]]>
- 無可用更新 - 選項 - 說明 - 程式庫 - 確認卸載 - 已卸載 - 擴展包卸載成功 - 卸載 - - 注意: 任何文檔,媒體或需要這些項目才能運作的物件將會停止運作,並可能使得系統不穩定, - 請小心移除。若有疑慮,請聯絡擴展包作者。]]> - 從程式庫下載更新 - 更新擴展包 - 更新說明 - 擴展包有可用的更新,您可以從程式庫網站更新。 - 版本 - 版本歷史 - 訪問擴展包網站 - 擴展包已安裝 - 這個擴展包無法安裝,它需要Umbraco至少是版本 %0% - 移除中... - 下載中... - 匯入中... - 安裝中... - 重新啟動中,請稍後... - 都好了,您的瀏覽器將重新整理,請稍待... - - - 帶格式粘貼(不推薦) - 您所粘貼的文本含有特殊字元或格式,Umbraco將清除以適應網頁。 - 無格式粘貼 - 粘貼並移除格式(推薦) - - - 基於角色的保護 - 請使用Umbraco的會員群組。]]> - 使用基於角色的授權需要首先建立會員組。 - 錯誤頁 - 當用戶登錄後訪問沒有許可權的頁時顯示該頁 - 選擇限制訪問此頁的方式 - %0% 現在處於受保護狀態 - %0% 的保護被取消 - 登錄頁 - 選擇公開的登錄入口 - 取消保護 - 選擇一個包含登錄表單和提示資訊的頁 - 選擇訪問該頁的角色類型 - 為此頁設置帳號和密碼 - 單用戶保護 - 如果您只希望提供一個用戶名和密碼就能訪問 - - - - - - - - 包含未發佈的子項 - 正在發佈,請稍候… - %0% 中的 %1% 頁面已發佈… - %0% 已發佈 - %0% 及其子項已發佈 - 發佈 %0% 及其子項 - 發佈按鈕來將%0%的內容設定為公開。

- 您可以同時發佈本頁以及其子項目若您點選下面的包含子頁。 - ]]>
- - - 您尚未設定任何許可顏色 - - - 輸入外部連結 - 選擇內部連結 - 標題 - 連結 - 新視窗 - 輸入新標題 - 輸入連結 - - - 重設 - - - 當前版本 - 紅色 文字將不會顯示於所選版本,而綠色表示增加部分。]]> - 文檔已回滾 - 這顯示所選版本的HTML格式,如果您想要比較兩版本的差異,請使用比較檢視 - 回滾至 - 選擇版本 - 查看 - - - 編輯腳本 - - - Concierge - 內容 - Courier - 開發 - 設定精靈 - 媒體 - 會員 - 消息 - 設置 - 統計 - 翻譯 - 用戶 - 說明 - 表單 - 統計 - - - 移至 - 說明主題為 - 影片主題為 - 最好的Umbraco影片教學 - - - 預設範本 - 字典鍵 - 要導入文檔類型,請點擊“流覽”按鈕,再點擊“導入”,然後在您電腦上查找 ".udt"檔導入(下一頁中需要您再次確認) - 新建選項卡標題 - 節點類型 - 類型 - 樣式表 - 腳本 - 樣式表屬性 - 選項卡 - 選項卡標題 - 選項卡 - 主控文件類型啟動 - 該文檔類型使用 - 作為主控文件類型. 主控文件類型的標籤只能在主控文件類型裡修改。 - 沒有欄位設置在該標籤頁 - 主文檔類別 - 新增對應範本 - 增加圖示 - - - 排列順序 - 增添時間 - 排序完成。 - 上下拖拽項目或按一下列頭進行排序 - - - - 驗證 - 驗證錯誤一定要修正才能儲存項目 - 失敗 - 使用者權限不足,無法完成操作 - 已取消 - 操作被協力廠商外掛程式取消 - 發佈被協力廠商外掛程式取消 - 屬性類型已存在 - 屬性類型已創建 - 資料類別:%1%]]> - 屬性類型已刪除 - 內容類別型已保存 - 選項卡已創建 - 選項卡已刪除 - id為%0%的選項卡已刪除 - 樣式表未保存 - 樣式表已保存 - 樣式表保存,無錯誤。 - 資料類型已保存 - 字典項已保存 - 因為上級頁面未發佈導致發佈失敗! - 內容已發佈 - 公眾可見 - 內容已保存 - 請發佈以使更改生效 - 提交審核 - 更改已提交審核 - 媒體已保存 - 媒體已保存 - 會員已保存 - 樣式表屬性已保存 - 樣式表已保存 - 範本已保存 - 保存使用者出錯(請查看日誌) - 用戶已保存 - 用戶類型已保存 - 檔未保存 - 檔無法保存,請檢查許可權。 - 檔保存 - 檔保存,無錯誤。 - 語言已保存 - 媒體類別已儲存 - 會員類別已儲存 - Python腳本未保存 - Python腳本因為錯誤未能保存 - Python已保存 - Python腳本無錯誤 - 範本未保存 - 範本別名相同 - 範本已保存 - 範本保存,無錯誤。 - 內容已取消發佈 - 片段視圖已保存 - 片段視圖保存,無錯誤。 - 片段視圖未保存 - 片段視圖因為錯誤未能保存 - 腳本視圖已儲存 - 腳本視圖已儲存,沒有任何錯誤! - 腳本視圖未儲存 - 儲存檔案時發生錯誤 - 儲存檔案時發生錯誤 - - - 使用CSS語法,如:h1、.redHeader、.blueTex。 - 編輯樣式表 - 編輯樣式屬性 - 編輯器中的樣式屬性名 - 預覽 - 樣式 - - - 編輯範本 - 插入內容區 - 插入內容預留位置 - 插入字典項 - 插入巨集 - 插入頁欄位 - 母版 - 範本標籤快速指南 - 範本 - - - Rich Text Editor - Image - Macro - Embed - Headline - Quote - 選擇內容類別 - 選擇排列方式 - 新增一行 - 新增內容 - 放棄內容 - 設定已儲存 - 此處不允許有內容 - 此處允許有內容 - 點選來內嵌 - 點選來插入圖片 - 圖片標題... - 在此填寫... - 網格排列方式 - 排列是指網格編輯器的整體工作區域,通常您只需要一種或兩種排列方式 - 增加網格排列方式 - 藉由設定列寬以及增加新的區域來調整排列方式 - 行設定 - 行是預先水平排列的格子 - 增加行設定 - 藉由設定小格寬度和增添小格來調整此行 - - 網格排列方式的列總數 - 設定 - 調整設定編輯器可以改變的項目 - 樣式 - 調整樣式編輯器可以改變的項目 - 當JSON格式正確時設定才可以儲存 - 允許所有編輯器 - 允許所有行設定 - 定為預設 - 選擇額外 - 選擇預設 - 已增加 - - - 組合 - 您沒有增加任何選項卡 - 增加新的選項卡 - 增加另外的選項卡 - 繼承的表格 - 增加屬性 - 必要標籤 - 允許清單檢視 - 允許內容項目顯示成可以排列及搜尋的清單,子項目不會被顯示 - 允許的範本 - 選擇哪些範本編輯器可以使用於此類別的內容 - 允許為根項目 - 允許編輯器新增此類別的內容為根項目 - 是的 - 允許此類別內容為根項目 - 允許子節點種類 - 允許某些特定種類能夠成為此種類內容的子項目 - 選擇子節點 - 從已存在的文檔類別中繼承選項卡以及屬性。新選項卡將被新增至目前文檔種類或合併至已存在同名的選項卡中。 - 此內容種類已經用於集合中,因此不能重複添加本身。 - 沒有可用於集合的內容種類。 - 可用的編輯器 - 重複使用 - 編輯器設定 - 設定 - 是,刪除 - 已移至下層 - 已複製至下層 - 選擇要移動的資料夾 - 選擇要複製的資料夾 - 至下方樹狀結構 - 所有文檔種類 - 所有文檔 - 所有媒體項目 - 使用此文檔種類的將被永久刪除,請確認您也想要將它們刪除。 - 使用此媒體種類的將被永久刪除,請確認您也想要將它們刪除。 - 使用此會員種類的將被永久刪除,請確認您也想要將它們刪除。 - 以及所有使用此種類的文件項目 - 以及所有使用此種類的媒體項目 - 以及所有使用此種類的會員項目 - 使用此編輯器將會套用新設定 - 會員可以編輯 - 顯示於會員資料 - - - 替代欄位 - 替代文本 - 大小寫 - 編碼 - 選取欄位 - 轉換分行符號 - 將換行符號取代成為HTML標籤 &lt;br&gt; - 自訂欄位 - 是,僅日期 - 格式化時間 - HTML編碼 - 將替換HTML中的特殊字元 - 將在欄位值後插入 - 將在欄位值前插入 - 小寫 - - 欄位後插入 - 欄位前插入 - 遞迴 - 標準欄位 - 大寫 - URL編碼 - 將格式化URL中的特殊字元 - 當上面欄位值為空時使用 - 該欄位僅在主欄位為空時使用 - 是,含時間,分隔符號為: - - - 標記為您的任務 - 指派給您。請按「翻譯詳情」或頁面名稱觀看包含回應的詳細檢視畫面。 - 您也可以將此頁面下載成為XML格式檔案,請按「下載XML」按鈕。
- 若想要關閉翻譯任務,請至細節頁面點選「結束」按鈕。 - ]]>
- 關閉任務 - 翻譯詳情 - 將翻譯任務下載為XML - 下載 XML - 下載 XML DTD - 欄位 - 包含子頁 - - [%0%]翻譯任務:%1% - 沒有翻譯員,請創建翻譯員角色的用戶。 - 您創建的任務 - 由您創建的頁面。要瀏覽包含回應的詳細檢視畫面, - 點選「詳情」或頁面名稱。您可以下載此頁面成為XML格式檔案,請點選「下載XML」連結。 - 若要關閉翻譯任務,請至詳情檢視並點選「關閉」按鈕。 - ]]> - 頁面'%0%'已經發送給翻譯 - 請選擇本內容應該被翻譯成的語言 - 發送頁面'%0%'以便翻譯 - 分配者 - 任務開啟 - 總字數 - 翻譯到 - 翻譯完成。 - 您可以流覽剛翻譯的頁面,如果原始頁存在,您將得到兩者的比較。 - 翻譯失敗,XML可能損壞了。 - 翻譯選項 - 翻譯員 - 上傳翻譯的xml - - - 緩存流覽 - 回收站 - 創建擴展包 - 資料類型 - 字典 - 已安裝的擴展包 - 安裝皮膚 - 安裝新手套件 - 語言 - 安裝本地擴展包 - 巨集 - 媒體類型 - 會員 - 會員組 - 角色 - 會員類型 - 文檔類型 - 相關類型 - 擴展包 - 擴展包 - Python文件 - 從線上程式庫安裝 - 安裝Runway - Runway模組 - Scripting文件 - 腳本 - 樣式表 - 範本 - 統計 - - - - - - 有可用更新 - %0%已就緒,點擊這裡下載 - 無到伺服器的連接 - 檢查更新失敗 - - - 管理員 - 分類欄位 - 更改密碼 - 更改密碼 - 確認新密碼 - 要改變密碼,請在框中輸入新密碼,然後按一下“更改密碼”。 - 內容頻道 - 描述欄位 - 禁用用戶 - 文檔類型 - 編輯 - 排除欄位 - 語言 - 登錄 - 默認打開媒體項 - 區域 - 禁用後臺管理介面 - 舊的密碼 - 密碼 - 重設密碼 - 您的密碼已更改! - 重輸密碼 - 輸入新密碼 - 新密碼不能為空! - 當前密碼 - 密碼錯誤 - 新密碼和重輸入的密碼不一致,請重試! - 重輸的密碼和原密碼不一致! - 替換子項許可權設置 - 您正在修改存取權限的頁面: - 選擇要修改許可權的頁 - 搜索子物件 - 預設打開內容項 - 用戶名 - 用戶許可權 - 撰稿人 - 翻譯者 - 改變 - 您的個人檔案 - 您的歷程記錄 - 連線到期於 - - - 驗證 - 以電子郵件驗證 - 以數字驗證 - 以網址驗證 - ...或輸入自訂驗證 - 必要欄位 - - - - 數值已設為推薦值:%0% - 在設定檔 %3% 中XPath %2% 的數值設為 %1% 。 - 在設定檔 %3% 中XPath %2% 的預期值設為 %1% ,但卻是 %0%。 - 在設定檔 %3% 中XPath %2% 的值為非預期值 %0%。 - - 自訂錯誤設定為 %0% - 自訂錯誤設定為 %0。建議在上線前改為 %1%。 - 自訂錯誤成功設定為 %0% - 巨集錯誤設為 %0% - 巨集錯誤設為 %0%,如此一來,當巨集有任何錯誤時會阻止某些或全部頁面正常載入。改正會將此設定 %1%。 - 巨集錯誤已設為 %0% - - 嘗試略過IIS自訂錯誤目前設為 %0%,而且您使用的IIS版本為 %1%。 - 嘗試略過IIS自訂錯誤目前設為 %0%,然而在您使用的IIS版本為 %2% 時,建議設定是 %1%。 - 嘗試略過IIS自訂錯誤已成功設為 %0%。 - - 檔案不存在:%0%。 - '%1%'中無法找到'%0%'。]]> - 有錯誤產生,請參閱下列錯誤的紀錄:%0%。 - 成員 - 所有XML:%0%,總共:%1%,不合格:%2% - 媒體 - 所有XML:%0%,總共發佈:%1%,不合格:%2% - 內容 - 所有XML:%0%,總共發佈:%1%,不合格:%2% - 憑證驗證錯誤:%0% - 網址探查錯誤:%0% - '%1%' - 您目前使用HTTPS瀏覽本站:%0% - 在您的web.config檔案中,appSetting的umbracoUseSSL是設為false。當您開始使用HTTPS時,應將其改為 true。 - 在您的web.config檔案中,appSetting的umbracoUseSSL是設為 %0%,您的cookies %0% 標成安全。 - 無法在您的web.config檔案中,更新appSetting的umbracoUseSSL設定,錯誤訊息:%0% - - 開啟HTTPS - 在web.config檔案中,將appSetting的umbracoUseSSL設true。 - 在您的web.config檔案中,appSetting的umbracoUseSSL已設為 true,您的cookies 將被標成安全。 - 修正 - 無法修正比較種類檢查為'ShouldNotEqual'。 - 用提供的數值無法修正比較種類檢查為'ShouldEqual'。 - 沒有提供要修正檢查的數值。 - 偵錯編輯模式關閉。 - 偵錯編輯模式目前已開啟。上線前建議將其關閉。 - 偵錯編輯模式已成功關閉。 - 詳細記錄模式已關閉。 - 詳細記錄模式目前已開啟。上線前建議將其關閉。 - 詳細記錄模式已成功關閉。 - 所有資料夾已有正確權限設定。 - - %0%。]]> - %0%。如果無須寫入,不需採取行動。]]> - 所有檔案已有正確權限設定。 - - %0%。]]> - %0%。如果無須寫入,不需採取行動。]]> - X-Frame-Options 設定能控制網站是否可以被其他人IFRAMEd已找到。]]> - X-Frame-Options 設定能控制網站是否可以被其他人IFRAMEd沒有找到。]]> - 調整設定的標頭 - 在 web.config 的 httpProtocol/customHeaders 區域增加設定來防止本站被別的網站IFRAMEd。 - 在 web.config 的 httpProtocol/customHeaders 區域已經增加設定來防止本站被別的網站IFRAMEd。 - 無法更新web.config檔案,錯誤:%0% - - %0%。]]> - 在標頭中沒有找到揭露網站技術的資訊。 - 在 Web.config 檔案中,找不到 system.net/mailsettings。 - 在 Web.config 檔案中的 system.net/mailsettings,沒有設定 host 。 - SMTP設定正確,而且服務正常運作。 - SMTP伺服器 %0% : %1% 無法連接。請確認在Web.config 檔案中 system.net/mailsettings 設定正確。 - %0%。]]> - %0%。]]> - - - 停止網址追蹤器 - 啟動網址追蹤器 - 原本網址 - 轉址成 - 沒有任何轉址 - 當發佈後的頁面改名或移動時,會自動轉址至新網頁。 - 移除 - 您確定要移除從 %0% 到 %1% 的轉址嗎? - 轉址已移除。 - 移除轉址錯誤。 - - 您確定要停止轉址追蹤器? - 轉址追蹤器已停止。 - 停止轉址追蹤器錯誤,更多資訊請參閱您的紀錄檔。 - 轉址追蹤器已開啟。 - 啟動轉址追蹤器錯誤,更多資訊請參閱您的紀錄檔。 - -
From 08818c63588f1e16c993d4ea32518c86dac14d46 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Fri, 5 Oct 2018 20:15:40 +1000 Subject: [PATCH 056/585] removes ng-show from iframe, make loader background a solid color to hide iframe while it loads --- src/Umbraco.Web.UI.Client/src/less/canvas-designer.less | 4 ++-- src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 78c1616bc6..cbb38a23b1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -81,9 +81,9 @@ a, a:hover{ .wait { display: block; - height: 280px; + height: 100%; width: 100%; - background: center center url(../img/loader.gif) no-repeat; + background:#fff center center url(../img/loader.gif) no-repeat; } /****************************/ diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 02209063fd..7f442cc333 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -20,7 +20,7 @@ @Html.Partial(Model.PreviewExtendedHeaderView) } -
+
From 6275a481aa22baeb28b2bace698e3f66da21cc97 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 9 Oct 2018 10:21:32 +0200 Subject: [PATCH 057/585] Fixed order by "updater" query. --- .../Persistence/Repositories/Implement/DocumentRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index d888f452ef..ebe86fe126 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -722,7 +722,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : filterClause.Item1; filterSql.Append( - where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})", + where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)," : $"AND ({where})", filterClause.Item2); } } @@ -861,7 +861,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin("updaterUser").On((version, user) => version.UserId == user.Id, aliasRight: "updaterUser"); // see notes in ApplyOrdering: the field MUST be selected + aliased - sql = Sql(InsertBefore(sql, "FROM", SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering"), sql.Arguments); + sql = Sql(InsertBefore(sql, "FROM", ", " + SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering "), sql.Arguments); sql = InsertJoins(sql, joins); From 471e47e11bb64ee4d0386e8937c02315ff1a1f64 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 9 Oct 2018 10:41:31 +0200 Subject: [PATCH 058/585] #3163 - Don't overwrite the label name even though labels are disabled in the color picker (#3164) --- .../src/views/components/umb-color-swatches.html | 2 +- .../propertyeditors/colorpicker/colorpicker.prevalues.html | 2 +- src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html index a89e51ab32..d038c9973c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html @@ -1,6 +1,6 @@ 
-
From 9b4f879b6d051391c1437e0133719b010ef6feb8 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Tue, 9 Oct 2018 12:28:43 +0200 Subject: [PATCH 065/585] Added XML documentation to the DatabaseSchemaHelper class (#3205) --- .../Persistence/DatabaseSchemaHelper.cs | 106 +++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs index 304cdc185f..f4cf7b23e9 100644 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -11,6 +10,9 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Persistence { + /// + /// Helper class for working with databases and schemas. + /// public class DatabaseSchemaHelper { private readonly Database _db; @@ -18,6 +20,22 @@ namespace Umbraco.Core.Persistence private readonly ISqlSyntaxProvider _syntaxProvider; private readonly BaseDataCreation _baseDataCreation; + /// + /// Intializes a new helper instance. + /// + /// The database to be used. + /// The logger. + /// The syntax provider. + /// + /// A new instance could be initialized like: + /// + /// var schemaHelper = new DatabaseSchemaHelper( + /// ApplicationContext.Current.DatabaseContext.Database, + /// ApplicationContext.Current.ProfilingLogger.Logger, + /// ApplicationContext.Current.DatabaseContext.SqlSyntax + /// ); + /// + /// public DatabaseSchemaHelper(Database db, ILogger logger, ISqlSyntaxProvider syntaxProvider) { _db = db; @@ -26,11 +44,41 @@ namespace Umbraco.Core.Persistence _baseDataCreation = new BaseDataCreation(db, logger); } + /// + /// Returns whether a table with the specified exists in the database. + /// + /// The name of the table. + /// true if the table exists; otherwise false. + /// + /// + /// if (schemaHelper.TableExist("MyTable")) + /// { + /// // do something when the table exists + /// } + /// + /// public bool TableExist(string tableName) { return _syntaxProvider.DoesTableExist(_db, tableName); } + /// + /// Returns whether the table for the specified exists in the database. + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// The type representing the DTO/table. + /// true if the table exists; otherwise false. + /// + /// + /// if (schemaHelper.TableExist<MyDto>) + /// { + /// // do something when the table exists + /// } + /// + /// public bool TableExist() { var poco = Database.PocoData.ForType(typeof(T)); @@ -90,6 +138,17 @@ namespace Umbraco.Core.Persistence _logger.Info("Finalized database schema creation"); } + /// Creates a new table in the database based on the type of . + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. + /// The type representing the DTO/table. + /// Whether the table should be overwritten if it already exists. public void CreateTable(bool overwrite) where T : new() { @@ -97,6 +156,16 @@ namespace Umbraco.Core.Persistence CreateTable(overwrite, tableType); } + /// + /// Creates a new table in the database based on the type of . + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, this method will not do anything. + /// + /// The type representing the DTO/table. public void CreateTable() where T : new() { @@ -104,6 +173,19 @@ namespace Umbraco.Core.Persistence CreateTable(false, tableType); } + /// + /// Creates a new table in the database for the specified . + /// + /// If has been decorated with an , the name from + /// that attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. + /// + /// Whether the table should be overwritten if it already exists. + /// The the representing the table. public void CreateTable(bool overwrite, Type modelType) { var tableDefinition = DefinitionFactory.GetTableDefinition(_syntaxProvider, modelType); @@ -189,6 +271,19 @@ namespace Umbraco.Core.Persistence } } + /// + /// Drops the table for the specified . + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// The type representing the DTO/table. + /// + /// + /// schemaHelper.DropTable<MyDto>); + /// + /// public void DropTable() where T : new() { @@ -204,6 +299,15 @@ namespace Umbraco.Core.Persistence DropTable(tableName); } + /// + /// Drops the table with the specified . + /// + /// The name of the table. + /// + /// + /// schemaHelper.DropTable("MyTable"); + /// + /// public void DropTable(string tableName) { var sql = new Sql(string.Format( From 9986c10cecbe6c0fd867891815dc5cda05adf724 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Mon, 8 Oct 2018 15:46:17 +0100 Subject: [PATCH 066/585] Cleaned up the view by removing inline styles and style tags on the view itself. Removed inline styles in the code behind files Moved all styles into the modals.less stylesheet --- .../src/less/modals.less | 57 +++++++++++++++++++ .../umbraco/dialogs/rollBack.aspx | 43 +------------- .../umbraco/dialogs/rollBack.aspx | 39 +------------ .../umbraco/dialogs/rollBack.aspx.cs | 10 ++-- 4 files changed, 66 insertions(+), 83 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index d4552c3ac5..d8f188dd53 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -105,6 +105,63 @@ border-top: 1px solid @purple-l3; } +.umb-dialog .propertyItemheader { + width: 140px !Important; +} + +.umb-dialog .diffDropdown +{ + width:400px; +} + +.umb-dialog .diffPanel { + height: 400px; +} + + +.umb-dialog .diff { + margin-top: 10px; + height: 100%; + overflow: auto; + border-top: 1px solid #ccc; + border-top: 1px solid #ccc; + padding: 5px; +} +.umb-dialog .diff table{ + width:95%; + max-width:95%; + margin: 0 3px; +} +.umb-dialog .diff table th { + padding: 5px; + width: 25%; + border-bottom: 1px solid #ccc; +} + +.umb-dialog .diff table td { + border-bottom: 1px solid #ccc; + padding: 3px; +} + +.umb-dialog .diff del { + background: rgb(255, 230, 230) none repeat scroll 0%; + -moz-background-clip: -moz-initial; + -moz-background-origin: -moz-initial; + -moz-background-inline-policy: -moz-initial; +} + +.umb-dialog .diff ins { + background: rgb(230, 255, 230) none repeat scroll 0%; + -moz-background-clip: -moz-initial; + -moz-background-origin: -moz-initial; + -moz-background-inline-policy: -moz-initial; +} + +.umb-dialog .diff .diffnotice { + text-align: center; + margin-bottom: 10px; +} + /*we will always make sure to wrap iframe dialogs in proper padding*/ .umbracoDialog{ width: auto !Important; diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/rollBack.aspx index 5709b47d10..f596121627 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/rollBack.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/rollBack.aspx @@ -13,43 +13,6 @@ var submitOnEnter = true; - @@ -63,7 +26,7 @@ () - + @@ -76,7 +39,7 @@ - +

@@ -84,7 +47,7 @@

- +
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx index 6b67e41ecb..ae4c8dd5b5 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx @@ -13,44 +13,7 @@ var submitOnEnter = true; - +
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs index b28e40163b..f6390a9d0b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs @@ -34,8 +34,8 @@ namespace umbraco.presentation.dialogs diffPanel.Visible = true; Document rollback = new Document(currentDoc.Id, new Guid(allVersions.SelectedValue)); - propertiesCompare.Text = "" + ui.Text("general", "name") + ":" + rollback.Text + ""; - propertiesCompare.Text += "" + ui.Text("content", "createDate") + ":" + rollback.VersionDate.ToLongDateString() + " " + rollback.VersionDate.ToLongTimeString() + " " + ui.Text("general", "by") + ": " + rollback.Writer.Name + ""; + propertiesCompare.Text = "" + ui.Text("general", "name") + ":" + rollback.Text + ""; + propertiesCompare.Text += "" + ui.Text("content", "createDate") + ":" + rollback.VersionDate.ToLongDateString() + " " + rollback.VersionDate.ToLongTimeString() + " " + ui.Text("general", "by") + ": " + rollback.Writer.Name + ""; if (rbl_mode.SelectedValue == "diff") lt_notice.Text = ui.Text("rollback", "diffHelp"); @@ -66,14 +66,14 @@ namespace umbraco.presentation.dialogs string cThevalue = library.StripHtml(cP.Value.ToString()); - propertiesCompare.Text += "" + p.PropertyType.Name + ":" + library.ReplaceLineBreaks(cms.businesslogic.utilities.Diff.Diff2Html(cThevalue, thevalue)) + ""; + propertiesCompare.Text += "" + p.PropertyType.Name + ":" + library.ReplaceLineBreaks(cms.businesslogic.utilities.Diff.Diff2Html(cThevalue, thevalue)) + ""; } else { //If no current version of the value... display with no diff. - propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; + propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; } @@ -81,7 +81,7 @@ namespace umbraco.presentation.dialogs else { //If display mode is html - propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; + propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; } //previewVersionContent.Controls.Add(new LiteralControl("

" + p.PropertyType.Name + "
")); From 7e16cf360a8388db3f030aa00de2c48ab947ab07 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Mon, 8 Oct 2018 23:32:35 +0100 Subject: [PATCH 067/585] Issue with the bottom border looking out of place on the select editor screen fixed --- .../src/less/components/umb-tabs.less | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less index e6773a3eb0..0a243b8978 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less @@ -1,15 +1,15 @@ -.umb-nav-tabs { - position: absolute; - z-index: 999; -} - -.umb-nav-tabs.-padding-left { - padding-left: 20px; -} - -.umb-tab-content { - padding-top: 20px; - position: relative; - top: 22px; - border-top: 1px solid @purple-l3; -} +.umb-nav-tabs { + position: absolute; + z-index: 999; +} + +.umb-nav-tabs.-padding-left { + padding-left: 20px; +} + +.umb-tab-content { + padding-top: 20px; + position: relative; + top: 31px; + border-top: 1px solid @purple-l3; +} From d1b1a1cf6d8e62bbfa148cf9c959309470753987 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 9 Oct 2018 14:23:09 +0200 Subject: [PATCH 068/585] #3178 - Make the Checkbox/TrueFalse property editor configurable (#3179) --- .../propertyeditors/boolean/boolean.controller.js | 2 +- .../src/views/propertyeditors/boolean/boolean.html | 6 +++++- .../PropertyEditors/TrueFalsePropertyEditor.cs | 13 ++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js index 7689d78431..c574e1424f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js @@ -1,4 +1,4 @@ -function booleanEditorController($scope, $rootScope, assetsService) { +function booleanEditorController($scope) { function setupViewModel() { $scope.renderModel = { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html index 10f388a0d0..ad6bc43cfe 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html @@ -1,6 +1,10 @@

+ on-click="toggle()" + show-labels="{{model.config.labelOn ? 'true': 'false'}}" + label-position="right" + label-on="{{model.config.labelOn}}" + label-off="{{model.config.labelOn}}">
diff --git a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs index 6a8e726911..350e80f4cb 100644 --- a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs @@ -15,6 +15,17 @@ namespace Umbraco.Web.PropertyEditors { [PreValueField("default", "Default Value", "boolean")] public string Default { get; set; } + + public TrueFalsePreValueEditor() + { + Fields.Add(new PreValueField() + { + Description = "Write a label text", + Key = "labelOn", + Name = "Label", + View = "textstring" + }); + } } } -} \ No newline at end of file +} From ee4a252b0742fb4d960d3f6bfa26365351ab5068 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 9 Oct 2018 21:19:55 +0200 Subject: [PATCH 069/585] Revert "Fixed udi's rendered in rte content (#2531)" This reverts commit b0374695b15c4532b3dba1e1f62b2075924ba187. --- .../Configuration/UmbracoConfig.cs | 2 +- .../UmbracoSettings/ContentElement.cs | 22 +++-------- .../UmbracoSettings/IContentSection.cs | 4 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 3 -- .../Web/TemplateUtilitiesTests.cs | 37 ++----------------- src/Umbraco.Tests/packages.config | 1 - .../Templates/TemplateUtilities.cs | 33 +---------------- 7 files changed, 13 insertions(+), 89 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs index 5b6d27b9c5..82d90073e8 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfig.cs @@ -202,4 +202,4 @@ namespace Umbraco.Core.Configuration //TODO: Add other configurations here ! } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 9fe35f7cb5..17523ab3a1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -139,8 +139,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings internal CommaDelimitedConfigurationElement DisallowedUploadFiles { get { return GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); } - } - + } + [ConfigurationProperty("allowedUploadFiles")] internal CommaDelimitedConfigurationElement AllowedUploadFiles { @@ -195,12 +195,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return GetOptionalTextElement("loginBackgroundImage", string.Empty); } } - [ConfigurationProperty("StripUdiAttributes")] - internal InnerTextConfigurationElement StripUdiAttributes - { - get { return GetOptionalTextElement("StripUdiAttributes", true); } - } - string IContentSection.NotificationEmailAddress { get { return Notifications.NotificationEmailAddress; } @@ -319,8 +313,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IEnumerable IContentSection.DisallowedUploadFiles { get { return DisallowedUploadFiles; } - } - + } + IEnumerable IContentSection.AllowedUploadFiles { get { return AllowedUploadFiles; } @@ -363,11 +357,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string IContentSection.LoginBackgroundImage { get { return LoginBackgroundImage; } - } - - bool IContentSection.StripUdiAttributes - { - get { return StripUdiAttributes; } - } + } + } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index bfcbabaccd..343e6ae9e1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -75,8 +75,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool EnablePropertyValueConverters { get; } string LoginBackgroundImage { get; } - - bool StripUdiAttributes { get; } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index abed9b8245..86c36a8d76 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -65,9 +65,6 @@ ..\packages\Examine.0.1.89\lib\net45\Examine.dll - - ..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 96461519d8..f2a04c5f0f 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,6 +1,6 @@ using System; +using System.Linq; using System.Web; -using HtmlAgilityPack; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -10,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Profiling; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Routing; @@ -33,14 +34,6 @@ namespace Umbraco.Tests.Web [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] //this one has an invalid char so won't match [TestCase("hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] - // with a-tag with data-udi attribute, that needs to be stripped - [TestCase("hello world ", "hello world ")] - // with a-tag with data-udi attribute spelled wrong, so don't need stripping - [TestCase("hello world ", "hello world ")] - // with a img-tag with data-udi id, that needs to be strippde - [TestCase("hello world ", "hello world ")] - // with a img-tag with data-udi id spelled wrong, so don't need stripping - [TestCase("hello world ", "hello world ")] public void ParseLocalLinks(string input, string result) { var serviceCtxMock = MockHelper.GetMockedServiceContext(); @@ -70,7 +63,7 @@ namespace Umbraco.Tests.Web //setup a quick mock of the WebRouting section Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "AutoLegacy")), //pass in the custom url provider - new[] { testUrlProvider.Object }, + new[]{ testUrlProvider.Object }, true)) { var output = TemplateUtilities.ParseInternalLinks(input, umbCtx.UrlProvider); @@ -78,27 +71,5 @@ namespace Umbraco.Tests.Web Assert.AreEqual(result, output); } } - - [Test] - public void StripDataUdiAttributesUsingSrtringOnLinks() - { - var input = "hello world "; - var expected = "hello world "; - - var result = TemplateUtilities.StripUdiDataAttributes(input); - - Assert.AreEqual(expected, result); - } - - [Test] - public void StripDataUdiAttributesUsingStringOnImages() - { - var input = "hello world "; - var expected = "hello world "; - - var result = TemplateUtilities.StripUdiDataAttributes(input); - - Assert.AreEqual(expected, result); - } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index e458795917..45afd3279c 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -3,7 +3,6 @@ - diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 39ac69989a..a7e6738374 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -1,6 +1,4 @@ -using HtmlAgilityPack; -using System; -using System.Runtime.CompilerServices; +using System; using System.Text.RegularExpressions; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -48,11 +46,6 @@ namespace Umbraco.Web.Templates { if (urlProvider == null) throw new ArgumentNullException("urlProvider"); - if(string.IsNullOrEmpty(text)) - { - return text; - } - // Parse internal links var tags = LocalLinkPattern.Matches(text); foreach (Match tag in tags) @@ -81,11 +74,6 @@ namespace Umbraco.Web.Templates } } - if (UmbracoConfig.For.UmbracoSettings().Content.StripUdiAttributes) - { - text = StripUdiDataAttributes(text); - } - return text; } @@ -114,9 +102,6 @@ namespace Umbraco.Web.Templates private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - private static readonly Regex UdiDataAttributePattern = new Regex("data-udi=\"[^\\\"]*\"", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - /// /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. /// @@ -160,21 +145,5 @@ namespace Umbraco.Web.Templates { return text.CleanForXss(ignoreFromClean); } - - /// - /// Strips data-udi attributes from rich text - /// - /// A html string - /// A string stripped from the data-uid attributes - public static string StripUdiDataAttributes(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } - - - return UdiDataAttributePattern.Replace(input, string.Empty); - } } } From 4d1b361f8fc722ee296fe1fa0b3b96f4c02a604e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 9 Oct 2018 21:32:54 +0200 Subject: [PATCH 070/585] Style Public access dialog (#3194) --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- .../umbraco/config/lang/en_us.xml | 2 +- .../umbraco/dialogs/protectPage.aspx | 114 +++++++++--------- .../umbraco/controls/dualSelectBox.cs | 9 ++ .../umbraco/dialogs/protectPage.aspx.cs | 55 +++++---- 5 files changed, 100 insertions(+), 82 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 477bce0931..47a3dc74f4 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1219,7 +1219,7 @@ To manage your website, simply open the Umbraco back office and start adding con Role based protection - using Umbraco's member groups.]]> + If you wish to control access to the page using role-based authentication, using Umbraco's member groups. You need to create a membergroup before you can use role-based authentication Error Page Used when people are logged on, but do not have access 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 b1fa2b84b3..a9db9768cf 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1217,7 +1217,7 @@ To manage your website, simply open the Umbraco back office and start adding con Role based protection - using Umbraco's member groups.]]> + If you wish to control access to the page using role-based authentication, using Umbraco's member groups. You need to create a membergroup before you can use role-based authentication Error Page Used when people are logged on, but do not have access diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx index 49e1a43ccf..bda6db5be3 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx @@ -75,15 +75,19 @@ - - + +
+ +
+ +
-
+
@@ -116,74 +120,72 @@ + +
+ - - -
- - - - -
- -
- - - -
- - -

Member name already exists, click Change to use a different name or Update to continue

-
-
- - - -

<%= umbraco.ui.Text("publicAccess", "paSelectRoles", UmbracoUser)%>

-
- - - -
- - - - - - - <%=umbraco.ui.Text("paLoginPageHelp")%> - -
- +
+ + + +
- - - - - - - <%=umbraco.ui.Text("paErrorPageHelp")%> - -
- +
+ + +
- - - + +

Member name already exists, click Change to use a different name or Update to continue

+
+ + + + +

<%= umbraco.ui.Text("publicAccess", "paSelectRoles", UmbracoUser)%>

+
+ + + +
+ + + + + + <%=umbraco.ui.Text("paLoginPageHelp")%> + +
+ +
+ + +
+ + + <%=umbraco.ui.Text("paErrorPageHelp")%> + +
+ +
+ +
+ +
+
- diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/dualSelectBox.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/dualSelectBox.cs index 34ad91303e..00bf9d0866 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/dualSelectBox.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/dualSelectBox.cs @@ -41,6 +41,15 @@ namespace umbraco.controls } } + public new int Height + { + set + { + _possibleValues.Height = new Unit(value); + _selectedValues.Height = new Unit(value); + } + } + protected override void CreateChildControls() { _possibleValues.ID = "posVals"; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs index 04fb9e6bf9..6fe22f0cd3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs @@ -41,7 +41,7 @@ namespace umbraco.presentation.umbraco.dialogs protected void selectMode(object sender, EventArgs e) { p_mode.Visible = false; - p_buttons.Visible = true; + p_setup.Visible = true; if (rb_simple.Checked) { @@ -124,14 +124,15 @@ namespace umbraco.presentation.umbraco.dialogs bt_protect.CommandName = "advanced"; } - p_buttons.Visible = true; p_mode.Visible = false; + p_setup.Visible = true; } } // Load up membergrouops _memberGroups.ID = "Membergroups"; _memberGroups.Width = 175; + _memberGroups.Height = 165; var selectedGroups = ""; // get roles from the membership provider @@ -160,7 +161,8 @@ namespace umbraco.presentation.umbraco.dialogs groupsSelector.Controls.Add(_memberGroups); - bt_protect.Text = ui.Text("update"); + bt_selectMode.Text = ui.Text("buttons", "select"); + bt_protect.Text = ui.Text("save"); bt_buttonRemoveProtection.Text = ui.Text("paRemoveProtection"); // Put user code to initialize the page here @@ -266,37 +268,33 @@ namespace umbraco.presentation.umbraco.dialogs } } - feedback.Text = ui.Text("publicAccess", "paIsProtected", new cms.businesslogic.CMSNode(pageId).Text) + "

" + ui.Text("closeThisWindow") + ""; + feedback_text.Text = ui.Text("publicAccess", "paIsProtected", new cms.businesslogic.CMSNode(pageId).Text); - p_buttons.Visible = false; - pane_advanced.Visible = false; - pane_simple.Visible = false; + p_setup.Visible = false; + p_feedback.Visible = true; var content = ApplicationContext.Current.Services.ContentService.GetById(pageId); //reloads the current node in the tree ClientTools.SyncTree(content.Path, true); //reloads the current node's children in the tree ClientTools.ReloadActionNode(false, true); - feedback.type = global::umbraco.uicontrols.Feedback.feedbacktype.success; } protected void buttonRemoveProtection_Click(object sender, System.EventArgs e) { int pageId = int.Parse(helper.Request("nodeId")); - p_buttons.Visible = false; - pane_advanced.Visible = false; - pane_simple.Visible = false; + p_setup.Visible = false; Access.RemoveProtection(pageId); - feedback.Text = ui.Text("publicAccess", "paIsRemoved", new cms.businesslogic.CMSNode(pageId).Text) + "

" + ui.Text("closeThisWindow") + ""; + feedback_text.Text = ui.Text("publicAccess", "paIsRemoved", new cms.businesslogic.CMSNode(pageId).Text); + p_feedback.Visible = true; var content = ApplicationContext.Current.Services.ContentService.GetById(pageId); //reloads the current node in the tree ClientTools.SyncTree(content.Path, true); //reloads the current node's children in the tree ClientTools.ReloadActionNode(false, true); - feedback.type = global::umbraco.uicontrols.Feedback.feedbacktype.success; } protected CustomValidator SimpleLoginNameValidator; @@ -312,13 +310,13 @@ namespace umbraco.presentation.umbraco.dialogs protected global::System.Web.UI.HtmlControls.HtmlInputHidden tempFile; ///

- /// feedback control. + /// p_feedback control. /// /// /// Auto-generated field. /// To modify move field declaration from designer file to code-behind file. /// - protected global::umbraco.uicontrols.Feedback feedback; + protected global::System.Web.UI.WebControls.Panel p_feedback; /// /// p_mode control. @@ -329,6 +327,15 @@ namespace umbraco.presentation.umbraco.dialogs /// protected global::System.Web.UI.WebControls.Panel p_mode; + /// + /// p_setup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel p_setup; + /// /// pane_chooseMode control. /// @@ -464,15 +471,6 @@ namespace umbraco.presentation.umbraco.dialogs /// protected global::System.Web.UI.WebControls.PlaceHolder groupsSelector; - /// - /// p_buttons control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel p_buttons; - /// /// pane_pages control. /// @@ -581,6 +579,15 @@ namespace umbraco.presentation.umbraco.dialogs /// protected global::System.Web.UI.WebControls.PlaceHolder js; + /// + /// feedback_text control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal feedback_text; + } } From fd1a9bb3656739dcacc86095e43844d6ecc09ab0 Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Tue, 9 Oct 2018 21:51:04 +0200 Subject: [PATCH 071/585] #3214 - umbracoUrlAlias with uppercase characters (#3220) --- .../Routing/ContentFinderByUrlAlias.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs index e0121a4b3c..a27b9bd5b4 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs @@ -54,10 +54,17 @@ namespace Umbraco.Web.Routing alias = alias.TrimStart('/'); var xpathBuilder = new StringBuilder(); - xpathBuilder.Append(XPathStringsDefinition.Root); + if (rootNodeId > 0) + { xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentById, rootNodeId); + } + else + { + xpathBuilder.Append(XPathStringsDefinition.Root); + } + XPathVariable var = null; if (alias.Contains('\'') || alias.Contains('"')) @@ -92,20 +99,20 @@ namespace Umbraco.Web.Routing { // legacy XML schema case 0: - DescendantDocumentById = "//node [@id={0}]"; + DescendantDocumentById = "id({0})"; DescendantDocumentByAlias = "//node[(" - + "contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',{0},')" - + " or contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',/{0},')" - + ")]"; + + "contains(concat(',',translate(translate(data [@alias='umbracoUrlAlias'], ' ', ''),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),','),',{0},')" + + " or contains(concat(',',translate(translate(data [@alias='umbracoUrlAlias'], ' ', ''),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),','),',/{0},')" + + ")]"; break; // default XML schema as of 4.10 case 1: - DescendantDocumentById = "//* [@isDoc and @id={0}]"; + DescendantDocumentById = "id({0})[@isDoc]"; DescendantDocumentByAlias = "//* [@isDoc and (" - + "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" - + " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" - + ")]"; + + "contains(concat(',',translate(translate(umbracoUrlAlias, ' ', ''),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),','),',{0},')" + + " or contains(concat(',',translate(translate(umbracoUrlAlias, ' ', ''),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),','),',/{0},')" + + ")]"; break; default: @@ -133,4 +140,4 @@ namespace Umbraco.Web.Routing #endregion } -} \ No newline at end of file +} From 3aece09e09564c3b7ba75f1e14b2a29cdbf6f89b Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 9 Oct 2018 22:25:42 +0200 Subject: [PATCH 072/585] 3223 - Minor tweaks and code alignment for the checkbox and color picker styling (#3224) * Make the focus effect on the toggle use the same box-shadow as is already being used in the color picker instead of doing it differently * Use the color variables instead of hard coded color and also make use of LESS' fade function outputting an rgba() color --- .../src/less/components/buttons/umb-toggle.less | 4 ++-- .../src/less/components/umb-color-swatches.less | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index 2ca03e2c79..150963cbb2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -11,8 +11,7 @@ } .umb-toggle:focus .umb-toggle__toggle{ - outline: 0; - box-shadow: 0 0 5px fade(@black, 30%); + box-shadow: 0 1px 3px fade(@black, 12%), 0 1px 2px fade(@black, 24%); } .umb-toggle__handler { @@ -35,6 +34,7 @@ background: @gray-8; border-radius: 90px; position: relative; + transition: box-shadow .3s; } .umb-toggle--checked .umb-toggle__toggle { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index d8e67444a1..43e62780a3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -4,7 +4,7 @@ .umb-color-box { border: 1px solid @gray-8; - color: white; + color: @white; cursor: pointer; padding: 1px; text-align: center; @@ -19,7 +19,7 @@ justify-content: center; &:hover, &:focus { - box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); + box-shadow: 0 1px 3px fade(@black, 12%), 0 1px 2px fade(@black, 24%); } &.active { @@ -56,7 +56,7 @@ padding-top: 10px; .umb-color-box__label { - background: #fff; + background: @white; font-size: 14px; display: flex; flex-flow: column wrap; From afa3e9b5ebbbed65479443011e6b2962913e5c66 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 9 Oct 2018 22:54:51 +0200 Subject: [PATCH 073/585] Improve image cropper UX (#3149) --- .../src/less/property-editors.less | 12 +++++++++- .../imagecropper/imagecropper.controller.js | 22 ++++++++++++++++++- .../imagecropper/imagecropper.html | 11 +++++----- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 +++- .../umbraco/config/lang/en_us.xml | 4 +++- 6 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index d14a1abae9..e3a8128671 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -315,7 +315,7 @@ div.umb-codeeditor .umb-btn-toolbar { .umb-mediapicker .umb-sortable-thumbnails li { flex-direction: column; - margin: 0 5px 5px 0; + margin: 0 0 5px 5px; padding: 5px; } @@ -574,6 +574,16 @@ div.umb-codeeditor .umb-btn-toolbar { } } + .imagecropper .umb-cropper__container .button-drawer { + display: flex; + justify-content: flex-end; + padding: 10px; + + button { + margin-left: 4px; + } + } + .umb-close-cropper { position: absolute; top: 3px; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index bc2dd91722..47442495f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -36,12 +36,32 @@ angular.module('umbraco') //crop a specific crop $scope.crop = function (crop) { - $scope.currentCrop = crop; + // clone the crop so we can discard the changes + $scope.currentCrop = angular.copy(crop); $scope.currentPoint = undefined; }; //done cropping $scope.done = function () { + if (!$scope.currentCrop) { + return; + } + // find the original crop by crop alias and update its coordinates + var editedCrop = _.find($scope.model.value.crops, function(crop) { + return crop.alias === $scope.currentCrop.alias; + }); + editedCrop.coordinates = $scope.currentCrop.coordinates; + $scope.close(); + }; + + //reset the current crop + $scope.reset = function() { + $scope.currentCrop.coordinates = undefined; + $scope.done(); + } + + //close crop overlay + $scope.close = function (crop) { $scope.currentCrop = undefined; $scope.currentPoint = undefined; }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html index 18f528fbd2..a8f513f006 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html @@ -18,8 +18,6 @@
- -
- - Reset - +
+ + + +
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 0e9fc09bcd..2ce6a31786 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -991,6 +991,8 @@ Mange hilsner fra Umbraco robotten Nulstil + Acceptér + Fortryd diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 47a3dc74f4..89ba5e0828 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1296,11 +1296,13 @@ To manage your website, simply open the Umbraco back office and start adding con Enter the link - Reset + Reset crop Define crop Give the crop an alias and its default width and height Save crop Add new crop + Done + Undo edits Current version 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 a9db9768cf..a42d4d0c60 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1294,11 +1294,13 @@ To manage your website, simply open the Umbraco back office and start adding con Enter the link - Reset + Reset crop Define crop Give the crop an alias and its default width and height Save crop Add new crop + Done + Undo edits Current version From 8fdbfea23ffcfa707a3529dddb000f5e286222ab Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 11:33:42 +0200 Subject: [PATCH 074/585] wip publish descendants dialog --- .../components/content/edit.controller.js | 26 +++++- .../services/contenteditinghelper.service.js | 15 +++- .../overlays/publishdescendants.controller.js | 26 ++++++ .../content/overlays/publishdescendants.html | 89 +++++++++++++++++++ .../Umbraco/config/lang/en_us.xml | 1 + 5 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index efb63fe1ac..98e02f3d55 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -170,7 +170,8 @@ saveAndPublish: $scope.saveAndPublish, sendToPublish: $scope.sendToPublish, unpublish: $scope.unpublish, - schedulePublish: $scope.schedule + schedulePublish: $scope.schedule, + publishDescendants: $scope.publishDescendants } }); @@ -628,6 +629,29 @@ } }; + $scope.publishDescendants = function() { + clearNotifications($scope.content); + //before we launch the dialog we want to execute all client side validations first + if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) { + var dialog = { + parentScope: $scope, + view: "views/content/overlays/publishdescendants.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "buttons_publishDescendants", + submit: function (model) { + model.submitButtonState = "busy"; + clearNotifications($scope.content); + model.submitButtonState = "success"; + }, + close: function () { + overlayService.close(); + } + }; + overlayService.open(dialog); + } + }; + $scope.preview = function (content) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 777b336447..a75e8d4a47 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -146,7 +146,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (!args.methods) { throw "args.methods is not defined"; } - if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish || !args.methods.schedulePublish) { + if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish || !args.methods.schedulePublish || !args.methods.publishDescendants) { throw "args.methods does not contain all required defined methods"; } @@ -200,6 +200,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica alias: "schedulePublish", addEllipsis: "true" }; + case "PUBLISH_DESCENDANTS": + // Publish descendants - it doesn't have a permission letter so + // the button letter is made unique so it doesn't collide with anything else + return { + letter: ch, + labelKey: "buttons_publishDescendants", + handler: args.methods.publishDescendants, + alias: "publishDescendant", + addEllipsis: "true" + }; default: return null; } @@ -210,7 +220,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //This is the ideal button order but depends on circumstance, we'll use this array to create the button list // Publish, SendToPublish - var buttonOrder = ["U", "H", "SCHEDULE"]; + var buttonOrder = ["U", "H", "SCHEDULE", "PUBLISH_DESCENDANTS"]; //Create the first button (primary button) //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. @@ -253,6 +263,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // get picked up by the loop through permissions if( _.contains(args.content.allowedActions, "U")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); + buttons.subButtons.push(createButtonDefinition("PUBLISH_DESCENDANTS")); } // if we are not creating, then we should add unpublish too, diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js new file mode 100644 index 0000000000..b6fcb8391f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js @@ -0,0 +1,26 @@ +(function () { + "use strict"; + + function PublishDescendantsController($scope, localizationService) { + + var vm = this; + + function onInit() { + + vm.variants = $scope.model.variants; + + if (!$scope.model.title) { + localizationService.localize("buttons_publishDescendants").then(function (value) { + $scope.model.title = value; + }); + } + + } + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.Overlays.PublishDescendantsController", PublishDescendantsController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html new file mode 100644 index 0000000000..a5b035c82c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -0,0 +1,89 @@ +
+ + +
+
+ Click Publish with descendants to publish {{vm.variants[0].name}} + and all content items underneath and thereby making their content publicly available. +
+ +
+
Include drafts
+ +
+ +
+ +
+ +
+ Click Publish with descendants to publish the selected languages + and the same languages of all content items underneath and thereby making their content publicly available. +
+ +
+ +
+ +
Languages
+ +
+ +
+ +
+ +
+ + +
+ + - +
+ +
+
{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}
+
+ +
+
{{notification.message}}
+
+ +
+
+ +
+
+
+ +
+ +
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 ae75d7af43..f78e25821d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -118,6 +118,7 @@ Insert macro Insert picture Publish and close + Publish with descendants Edit relations Return to list Save From e2d3cbc8eb0d8bb16807781f17b213ec6292435c Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 12:24:00 +0100 Subject: [PATCH 075/585] Decorate other settings trees with the correct grouping --- src/Umbraco.Web/Trees/MacrosTreeController.cs | 2 +- src/Umbraco.Web/Trees/MemberTypeTreeController.cs | 1 + src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs | 2 +- src/Umbraco.Web/Trees/RelationTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/ScriptsTreeController.cs | 1 + src/Umbraco.Web/Trees/StylesheetsTreeController.cs | 1 + 6 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index 66f92ffdc0..ebe31d91d9 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Macros)] [Tree(Constants.Applications.Settings, Constants.Trees.Macros, "Macros", sortOrder: 4)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class MacrosTreeController : TreeController { diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 56b836ce8a..77eb6e0b24 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -7,6 +7,7 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { + [CoreTree(TreeGroup =Constants.Trees.Groups.Settings)] [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, null, sortOrder: 2)] public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs index 882cfb2c9f..c874b01244 100644 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Settings, Constants.Trees.PartialViewMacros, null, sortOrder: 8)] [UmbracoTreeAuthorize(Constants.Trees.PartialViewMacros)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class PartialViewMacrosTreeController : PartialViewsTreeController { protected override IFileSystem FileSystem => Current.FileSystems.MacroPartialsFileSystem; diff --git a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs index e35a9a23b6..4930b4dc5e 100644 --- a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.RelationTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.RelationTypes, null, sortOrder: 5)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class RelationTypeTreeController : TreeController { protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/ScriptsTreeController.cs b/src/Umbraco.Web/Trees/ScriptsTreeController.cs index 97053993b4..08a03ac912 100644 --- a/src/Umbraco.Web/Trees/ScriptsTreeController.cs +++ b/src/Umbraco.Web/Trees/ScriptsTreeController.cs @@ -6,6 +6,7 @@ using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees { + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, "Scripts", "icon-folder", "icon-folder", sortOrder: 10)] public class ScriptsTreeController : FileSystemTreeController { diff --git a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs index 365f427e18..548e8ae928 100644 --- a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs +++ b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs @@ -4,6 +4,7 @@ using Umbraco.Web.Composing; namespace Umbraco.Web.Trees { + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, "Stylesheets", "icon-folder", "icon-folder", sortOrder: 9)] public class StylesheetsTreeController : FileSystemTreeController { From f1b20ebbb2fdf105f11fe9b31893b74804da35ce Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 12:25:19 +0100 Subject: [PATCH 076/585] Orders the grouped trees by name (which means the custom trees will be last now) Displays custom third party group - only if we have items to show in that group --- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 7eeb19893e..940f65730a 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -27,7 +27,6 @@ namespace Umbraco.Web.Trees Current.Services.ApplicationTreeService.GetAllTypes() .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute(false)?.TreeGroup)) .GroupBy(x => x.TreeGroup) - .OrderByDescending(x => x.Key) .ToList()); @@ -135,13 +134,16 @@ namespace Umbraco.Web.Trees treeGroupName = "thirdPartyGroup"; } - var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); - groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); + if (groupNodeCollection.Any()) + { + var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); + groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); - rootNodeGroups.Add(groupRoot); + rootNodeGroups.Add(groupRoot); + } } - return rootNodeGroups; + return rootNodeGroups.OrderBy(x => x.Name); } /// From 75d5c7e30caabe79e06834db4d4565a60928b861 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 14:48:54 +0200 Subject: [PATCH 077/585] add translations, autoslect active variant, order by active + clean up --- .../localization/localize.directive.js | 6 ++- .../overlays/publishdescendants.controller.js | 37 +++++++++++++++++++ .../content/overlays/publishdescendants.html | 30 +++++---------- .../Umbraco/config/lang/en_us.xml | 4 +- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js index 3833dc50b9..c3093eee9e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js @@ -31,13 +31,15 @@ angular.module("umbraco.directives") return { restrict: 'E', scope:{ - key: '@' + key: '@', + tokens: '=' }, replace: true, link: function (scope, element, attrs) { var key = scope.key; - localizationService.localize(key).then(function(value){ + var tokens = scope.tokens ? scope.tokens : null; + localizationService.localize(key, tokens).then(function(value){ element.html(value); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js index b6fcb8391f..0536863e6b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js @@ -7,16 +7,53 @@ function onInit() { + vm.includeUnpublished = false; vm.variants = $scope.model.variants; + vm.labels = {}; if (!$scope.model.title) { localizationService.localize("buttons_publishDescendants").then(function (value) { $scope.model.title = value; }); } + + if (vm.variants.length > 1) { + + //now sort it so that the current one is at the top + vm.variants = _.sortBy(vm.variants, function (v) { + return v.active ? 0 : 1; + }); + + var active = _.find(vm.variants, function (v) { + return v.active; + }); + + if (active) { + //ensure that the current one is selected + active.publishDescendants = true; + active.save = true; + } + + } else { + // localize help text for invariant content + vm.labels.help = { + "key": "content_publishDescendantsHelp", + "tokens": [] + }; + // add the node name as a token so it will show up in the translated text + vm.labels.help.tokens.push(vm.variants[0].name); + } } + //when this dialog is closed, reset all 'publish' flags + $scope.$on('$destroy', function () { + for (var i = 0; i < vm.variants.length; i++) { + vm.variants[i].publishDescendants = false; + vm.variants[i].save = false; + } + }); + onInit(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html index a5b035c82c..ba515c892c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -1,24 +1,17 @@
-
- Click Publish with descendants to publish {{vm.variants[0].name}} - and all content items underneath and thereby making their content publicly available. +

-
Include drafts
@@ -27,21 +20,16 @@
- Click Publish with descendants to publish the selected languages - and the same languages of all content items underneath and thereby making their content publicly available. +

@@ -55,7 +43,7 @@ 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 f78e25821d..b76bed49cd 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -226,6 +226,8 @@ Published Published (pending changes)> Publication Status + Publish with descendants to publish %0% and all content items underneath and thereby making their content publicly available.]]> + Publish with descendants to publish the selected languages and the same languages of content items underneath and thereby making their content publicly available.]]> Publish at Unpublish at Clear Date @@ -252,6 +254,7 @@ Add another text box Remove this text box Content root + Include drafts: also publish unpublished content items. This value is hidden. If you need access to view this value please contact your website administrator. This value is hidden. What languages would you like to publish? @@ -1148,7 +1151,6 @@ To manage your website, simply open the Umbraco back office and start adding con - Include unpublished subpages Publishing in progress - please wait... %0% out of %1% pages have been published... %0% has been published From 5a4225900548271473bff74e172b707d5d0c85f4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 15:02:03 +0200 Subject: [PATCH 078/585] disable button if nothing is selected --- .../overlays/publishdescendants.controller.js | 20 +++++++++++++++++++ .../content/overlays/publishdescendants.html | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js index 0536863e6b..4647dca093 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js @@ -5,6 +5,8 @@ var vm = this; + vm.changeSelection = changeSelection; + function onInit() { vm.includeUnpublished = false; @@ -46,6 +48,24 @@ } + /** Returns true if publishing is possible based on if there are un-published mandatory languages */ + function canPublish() { + var selected = []; + for (var i = 0; i < vm.variants.length; i++) { + var variant = vm.variants[i]; + if (variant.publishDescendants) { + selected.push(variant.publishDescendants); + } + } + return selected.length > 0; + } + + function changeSelection(variant) { + $scope.model.disableSubmitButton = !canPublish(); + //need to set the Save state to true if publish is true + variant.save = variant.publishDescendants; + } + //when this dialog is closed, reset all 'publish' flags $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html index ba515c892c..285dc1fc84 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -33,7 +33,9 @@
-
Languages
+
+ +
From 6db8f50ce36859b15aa4d4c04b8e3272572e13e7 Mon Sep 17 00:00:00 2001 From: elitsa Date: Wed, 10 Oct 2018 15:13:46 +0200 Subject: [PATCH 079/585] Right arrow appears when there are child nodes --- src/Umbraco.Web/Trees/MacrosTreeController.cs | 9 +++++++++ src/Umbraco.Web/Trees/MemberGroupTreeController.cs | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index 66f92ffdc0..11197fffb5 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net.Http.Formatting; using Umbraco.Core; using Umbraco.Core.Models; @@ -19,6 +20,14 @@ namespace Umbraco.Web.Trees public class MacrosTreeController : TreeController { + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + //check if there are any macros + root.HasChildren = Services.MacroService.GetAll().Any(); + return root; + } + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { var nodes = new TreeNodeCollection(); diff --git a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs index b9910c7b31..9c8c8ea4e0 100644 --- a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs @@ -19,5 +19,13 @@ namespace Umbraco.Web.Trees .OrderBy(x => x.Name) .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", false)); } + + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + //check if there are any groups + root.HasChildren = Services.MemberGroupService.GetAll().Any(); + return root; + } } } From 5c6bfe823d5f6f192834988a50bb4c6dbea05cbc Mon Sep 17 00:00:00 2001 From: elitsa Date: Wed, 10 Oct 2018 15:23:40 +0200 Subject: [PATCH 080/585] Configuring the logi behind "Send to Publish". --- .../components/content/edit.controller.js | 88 +++++++++++-------- .../overlays/sendtopublish.controller.js | 5 +- .../views/content/overlays/sendtopublish.html | 2 +- src/Umbraco.Web.UI/web.Template.config | 5 +- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 69c871f5ce..f452e6b812 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -46,7 +46,7 @@ $scope.ancestors = anc; }); $scope.$watch('culture', - function(value, oldValue) { + function (value, oldValue) { entityResource.getAncestors(content.id, "document", value) .then(function (anc) { $scope.ancestors = anc; @@ -154,7 +154,7 @@ // only create the save/publish/preview buttons if the // content app is "Conent" - if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") { + if (app && app.alias !== "umbContent" && app.alias !== "umbInfo") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showSaveButton = false; @@ -163,7 +163,7 @@ } // create the save button - if(_.contains($scope.content.allowedActions, "A")) { + if (_.contains($scope.content.allowedActions, "A")) { $scope.page.showSaveButton = true; // add ellipsis to the save button if it opens the variant overlay $scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false"; @@ -231,7 +231,7 @@ } } - function checkValidility(){ + function checkValidility() { //Get all controls from the 'contentForm' var allControls = $scope.contentForm.$getControls(); @@ -240,7 +240,7 @@ //Exclude known formControls 'contentHeaderForm' and 'tabbedContentForm' //Check property - $name === "contentHeaderForm" - allControls = _.filter(allControls, function(obj){ + allControls = _.filter(allControls, function (obj) { return obj.$name !== 'contentHeaderForm' && obj.$name !== 'tabbedContentForm' && obj.hasOwnProperty('$submitted'); }); @@ -258,26 +258,26 @@ } //Controls is the - function recurseFormControls(controls, array){ + function recurseFormControls(controls, array) { //Loop over the controls for (var i = 0; i < controls.length; i++) { var controlItem = controls[i]; //Check if the controlItem has a property '' - if(controlItem.hasOwnProperty('$submitted')){ + if (controlItem.hasOwnProperty('$submitted')) { //This item is a form - so lets get the child controls of it & recurse again var childFormControls = controlItem.$getControls(); recurseFormControls(childFormControls, array); } else { //We can assume its a field on a form - if(controlItem.hasOwnProperty('$error')){ + if (controlItem.hasOwnProperty('$error')) { //Set the validlity of the error/s to be valid //String of keys of error invalid messages var errorKeys = []; - for(var key in controlItem.$error){ + for (var key in controlItem.$error) { errorKeys.push(key); controlItem.$setValidity(key, true); } @@ -293,7 +293,7 @@ return array; } - function resetNestedFieldValiation(array){ + function resetNestedFieldValiation(array) { for (var i = 0; i < array.length; i++) { var item = array[i]; //Item is an object containing two props @@ -301,7 +301,7 @@ var fieldControl = item.control; var fieldErrorKeys = item.errorKeys; - for(var i = 0; i < fieldErrorKeys.length; i++) { + for (var i = 0; i < fieldErrorKeys.length; i++) { fieldControl.$setValidity(fieldErrorKeys[i], false); } } @@ -309,7 +309,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid @@ -416,7 +416,7 @@ }); } - $scope.unpublish = function() { + $scope.unpublish = function () { clearNotifications($scope.content); if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { var dialog = { @@ -428,9 +428,9 @@ submit: function (model) { model.submitButtonState = "busy"; - - var selectedVariants = _.filter(model.variants, function(variant) { return variant.save; }); - var culturesForUnpublishing = _.map(selectedVariants, function(variant) { return variant.language.culture; }); + + var selectedVariants = _.filter(model.variants, function (variant) { return variant.save; }); + var culturesForUnpublishing = _.map(selectedVariants, function (variant) { return variant.language.culture; }); contentResource.unpublish($scope.content.id, culturesForUnpublishing) .then(function (data) { @@ -444,8 +444,8 @@ }, function (err) { $scope.page.buttonGroupState = 'error'; }); - - + + }, close: function () { overlayService.close(); @@ -454,7 +454,7 @@ overlayService.open(dialog); } }; - + $scope.sendToPublish = function () { clearNotifications($scope.content); if (showSaveOrPublishDialog()) { @@ -471,7 +471,23 @@ model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response - console.log("saving need to happen here"); + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, function (err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); }, close: function () { overlayService.close(); @@ -483,10 +499,10 @@ } else { $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.sendToPublish, - action: "sendToPublish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -542,10 +558,10 @@ //ensure the publish flag is set $scope.content.variants[0].publish = true; $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.publish, - action: "publish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.publish, + action: "publish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -600,13 +616,13 @@ else { $scope.page.saveButtonState = "busy"; return performSave({ - saveMethod: $scope.saveMethod(), - action: "save" - }).then(function(){ - $scope.page.saveButtonState = "success"; - }, function () { - $scope.page.saveButtonState = "error"; - }); + saveMethod: $scope.saveMethod(), + action: "save" + }).then(function () { + $scope.page.saveButtonState = "success"; + }, function () { + $scope.page.saveButtonState = "error"; + }); } }; @@ -729,7 +745,7 @@ * Call back when a content app changes * @param {any} app */ - $scope.appChanged = function(app) { + $scope.appChanged = function (app) { createButtons($scope.content, app); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js index 6816e1430d..d608ed7f27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js @@ -34,7 +34,7 @@ if (active) { //ensure that the current one is selected - active.sendToPublish = true; + active.save = true; } } else { @@ -48,7 +48,7 @@ function changeSelection() { var firstSelected = _.find(vm.variants, function (v) { - return v.sendToPublish; + return v.save; }); $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected } @@ -73,6 +73,7 @@ $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { vm.variants[i].sendToPublish = false; + vm.variants[i].save = false; } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html index 71877cc907..bbe6609713 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html @@ -16,7 +16,7 @@ diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 844855f10f..943570376f 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -82,8 +82,9 @@ - - + + + From f0bbce3117ef537ddc3af602f291699afa64a109 Mon Sep 17 00:00:00 2001 From: elitsa Date: Wed, 10 Oct 2018 15:23:40 +0200 Subject: [PATCH 081/585] Configuring the logic behind "Send to Publish". --- .../components/content/edit.controller.js | 88 +++++++++++-------- .../overlays/sendtopublish.controller.js | 5 +- .../views/content/overlays/sendtopublish.html | 2 +- src/Umbraco.Web.UI/web.Template.config | 5 +- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 69c871f5ce..f452e6b812 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -46,7 +46,7 @@ $scope.ancestors = anc; }); $scope.$watch('culture', - function(value, oldValue) { + function (value, oldValue) { entityResource.getAncestors(content.id, "document", value) .then(function (anc) { $scope.ancestors = anc; @@ -154,7 +154,7 @@ // only create the save/publish/preview buttons if the // content app is "Conent" - if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") { + if (app && app.alias !== "umbContent" && app.alias !== "umbInfo") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showSaveButton = false; @@ -163,7 +163,7 @@ } // create the save button - if(_.contains($scope.content.allowedActions, "A")) { + if (_.contains($scope.content.allowedActions, "A")) { $scope.page.showSaveButton = true; // add ellipsis to the save button if it opens the variant overlay $scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false"; @@ -231,7 +231,7 @@ } } - function checkValidility(){ + function checkValidility() { //Get all controls from the 'contentForm' var allControls = $scope.contentForm.$getControls(); @@ -240,7 +240,7 @@ //Exclude known formControls 'contentHeaderForm' and 'tabbedContentForm' //Check property - $name === "contentHeaderForm" - allControls = _.filter(allControls, function(obj){ + allControls = _.filter(allControls, function (obj) { return obj.$name !== 'contentHeaderForm' && obj.$name !== 'tabbedContentForm' && obj.hasOwnProperty('$submitted'); }); @@ -258,26 +258,26 @@ } //Controls is the - function recurseFormControls(controls, array){ + function recurseFormControls(controls, array) { //Loop over the controls for (var i = 0; i < controls.length; i++) { var controlItem = controls[i]; //Check if the controlItem has a property '' - if(controlItem.hasOwnProperty('$submitted')){ + if (controlItem.hasOwnProperty('$submitted')) { //This item is a form - so lets get the child controls of it & recurse again var childFormControls = controlItem.$getControls(); recurseFormControls(childFormControls, array); } else { //We can assume its a field on a form - if(controlItem.hasOwnProperty('$error')){ + if (controlItem.hasOwnProperty('$error')) { //Set the validlity of the error/s to be valid //String of keys of error invalid messages var errorKeys = []; - for(var key in controlItem.$error){ + for (var key in controlItem.$error) { errorKeys.push(key); controlItem.$setValidity(key, true); } @@ -293,7 +293,7 @@ return array; } - function resetNestedFieldValiation(array){ + function resetNestedFieldValiation(array) { for (var i = 0; i < array.length; i++) { var item = array[i]; //Item is an object containing two props @@ -301,7 +301,7 @@ var fieldControl = item.control; var fieldErrorKeys = item.errorKeys; - for(var i = 0; i < fieldErrorKeys.length; i++) { + for (var i = 0; i < fieldErrorKeys.length; i++) { fieldControl.$setValidity(fieldErrorKeys[i], false); } } @@ -309,7 +309,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid @@ -416,7 +416,7 @@ }); } - $scope.unpublish = function() { + $scope.unpublish = function () { clearNotifications($scope.content); if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { var dialog = { @@ -428,9 +428,9 @@ submit: function (model) { model.submitButtonState = "busy"; - - var selectedVariants = _.filter(model.variants, function(variant) { return variant.save; }); - var culturesForUnpublishing = _.map(selectedVariants, function(variant) { return variant.language.culture; }); + + var selectedVariants = _.filter(model.variants, function (variant) { return variant.save; }); + var culturesForUnpublishing = _.map(selectedVariants, function (variant) { return variant.language.culture; }); contentResource.unpublish($scope.content.id, culturesForUnpublishing) .then(function (data) { @@ -444,8 +444,8 @@ }, function (err) { $scope.page.buttonGroupState = 'error'; }); - - + + }, close: function () { overlayService.close(); @@ -454,7 +454,7 @@ overlayService.open(dialog); } }; - + $scope.sendToPublish = function () { clearNotifications($scope.content); if (showSaveOrPublishDialog()) { @@ -471,7 +471,23 @@ model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response - console.log("saving need to happen here"); + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, function (err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); }, close: function () { overlayService.close(); @@ -483,10 +499,10 @@ } else { $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.sendToPublish, - action: "sendToPublish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -542,10 +558,10 @@ //ensure the publish flag is set $scope.content.variants[0].publish = true; $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.publish, - action: "publish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.publish, + action: "publish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -600,13 +616,13 @@ else { $scope.page.saveButtonState = "busy"; return performSave({ - saveMethod: $scope.saveMethod(), - action: "save" - }).then(function(){ - $scope.page.saveButtonState = "success"; - }, function () { - $scope.page.saveButtonState = "error"; - }); + saveMethod: $scope.saveMethod(), + action: "save" + }).then(function () { + $scope.page.saveButtonState = "success"; + }, function () { + $scope.page.saveButtonState = "error"; + }); } }; @@ -729,7 +745,7 @@ * Call back when a content app changes * @param {any} app */ - $scope.appChanged = function(app) { + $scope.appChanged = function (app) { createButtons($scope.content, app); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js index 6816e1430d..d608ed7f27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js @@ -34,7 +34,7 @@ if (active) { //ensure that the current one is selected - active.sendToPublish = true; + active.save = true; } } else { @@ -48,7 +48,7 @@ function changeSelection() { var firstSelected = _.find(vm.variants, function (v) { - return v.sendToPublish; + return v.save; }); $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected } @@ -73,6 +73,7 @@ $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { vm.variants[i].sendToPublish = false; + vm.variants[i].save = false; } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html index 71877cc907..bbe6609713 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html @@ -16,7 +16,7 @@ diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 844855f10f..943570376f 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -82,8 +82,9 @@ - - + + + From 5b3db88df9cff795d4f8641d32bf517db9650d2f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 15:02:50 +0100 Subject: [PATCH 082/585] Fix logic - so that Content Blueprints tree is added to the list of trees (Finding by name/title was not working, so switched to the AdditionalData["treeAlias"] instead --- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 940f65730a..4df0ee0f6a 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -116,7 +116,7 @@ namespace Umbraco.Web.Trees if (findAppTree != null) { //Now we need to get the 'TreeNode' which is in 'collection' - var treeItemNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); + var treeItemNode = collection.SingleOrDefault(x => x.AdditionalData["treeAlias"].ToString() == findAppTree.Alias); if (treeItemNode != null) { From c69590fd215efbbd12dd32089aa553440b8893cd Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 15:03:14 +0100 Subject: [PATCH 083/585] Move macro tree from Templating up into Settings group --- src/Umbraco.Web/Trees/MacrosTreeController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index ebe31d91d9..5d0a4f6315 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -15,9 +15,8 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Macros)] [Tree(Constants.Applications.Settings, Constants.Trees.Macros, "Macros", sortOrder: 4)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] - public class - MacrosTreeController : TreeController + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + public class MacrosTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { From 4da6788cb8799e927e7a36282e660a0fe2798867 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 16:27:13 +0100 Subject: [PATCH 084/585] Fix PartialView Tree Controller to display a folder icon as opposed to an article icon for nested folders - looks odd/broken to me otherwise --- src/Umbraco.Web/Trees/PartialViewsTreeController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs index ffb1d5e09b..6a65f5dd3e 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Trees { //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; - treeNode.Icon = "icon-article"; + treeNode.Icon = "icon-folder"; } } } From 9572d195b5abcff38d2967b94953efe8025e1389 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 09:16:44 +0100 Subject: [PATCH 085/585] Fixes based on Shan's feedback of the PR Only does fancy group logic if more than one group - means other sections can be grouped in the future --- .../Trees/ApplicationTreeController.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 4df0ee0f6a..7fedc44a15 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -22,6 +22,11 @@ namespace Umbraco.Web.Trees [PluginController("UmbracoTrees")] public class ApplicationTreeController : UmbracoAuthorizedApiController { + /// + /// Fetches all registered trees and groups them together if they have a [CoreTree] + /// Attribute with a 'TreeGroup' property set + /// This allows the settings section trees to be grouped by Settings, Templating & Other + /// private static readonly Lazy>> CoreTrees = new Lazy>>(() => Current.Services.ApplicationTreeService.GetAllTypes() @@ -86,8 +91,8 @@ namespace Umbraco.Web.Trees } } - //Don't apply fancy grouping logic futher down, if we are not 'settings' section - if(application != Constants.Applications.Settings) + //Don't apply fancy grouping logic futher down, if we only have one group of items + if (CoreTrees.Value.Count() == 1) { var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); multiTree.Name = Services.TextService.Localize("sections/" + application); @@ -95,28 +100,23 @@ namespace Umbraco.Web.Trees rootNodeGroups.Add(multiTree); return rootNodeGroups; } - - //For settings section only - //Group trees by [CoreTree] attribute - - //Core Trees contains all trees for all sections/applications + + //Group trees by [CoreTree] attribute with a TreeGroup property foreach(var treeSectionGroup in CoreTrees.Value) { var treeGroupName = treeSectionGroup.Key; var groupNodeCollection = new TreeNodeCollection(); - - //Only add trees to a new collection if they are from 'settings' foreach (var treeItem in treeSectionGroup) { //Item1 tuple - is the type from all tree types var treeItemType = treeItem.Item1; - var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == treeItemType); + var findAppTree = appTrees.FirstOrDefault(x => x.GetRuntimeType() == treeItemType); if (findAppTree != null) { //Now we need to get the 'TreeNode' which is in 'collection' - var treeItemNode = collection.SingleOrDefault(x => x.AdditionalData["treeAlias"].ToString() == findAppTree.Alias); + var treeItemNode = collection.FirstOrDefault(x => x.AdditionalData["treeAlias"].ToString() == findAppTree.Alias); if (treeItemNode != null) { @@ -134,7 +134,7 @@ namespace Umbraco.Web.Trees treeGroupName = "thirdPartyGroup"; } - if (groupNodeCollection.Any()) + if (groupNodeCollection.Count > 0) { var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); From eb3fd54e2e033720acc52f10ad196eb2fb18d95f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 09:21:39 +0100 Subject: [PATCH 086/585] Remove empty/unused keys for these tree header groupings --- src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml | 15 +- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 3 - src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 15 +- src/Umbraco.Web.UI/Umbraco/config/lang/es.xml | 179 +++++++------ src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 237 +++++++++--------- src/Umbraco.Web.UI/Umbraco/config/lang/he.xml | 29 +-- src/Umbraco.Web.UI/Umbraco/config/lang/it.xml | 13 +- src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml | 3 - src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml | 23 +- src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml | 3 - src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml | 105 ++++---- src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml | 135 +++++----- src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml | 17 +- src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml | 225 ++++++++--------- src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml | 3 - src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml | 25 +- src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml | 15 +- .../Umbraco/config/lang/zh_tw.xml | 3 - 18 files changed, 497 insertions(+), 551 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index 95a0a4d661..b218d19ee0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -446,7 +446,7 @@ Stiskněte tlačítko povýšit pro povýšení Vaší databáze na Umbraco %0%

Neobávejte se - žádný obsah nebude odstraněn a všechno bude fungovat jak má! -

+

]]> Stiskněte Následující pro pokračování. ]]> následující, pro pokračování konfiguračního průvodce]]> @@ -482,7 +482,7 @@ ]]> Chci začít od nuly zjistěte jak) Stále se můžete později rozhodnout nainstalovat Runway. Za tím účelem navštivte Vývojářskou sekci a zvolte Balíčky. ]]> @@ -516,7 +516,7 @@ Abyste získali pomoc od naší oceňované komunity, projděte si dokumentaci, nebo si pusťte některá videa zdarma o tom, jak vytvořit jednoduchý web, jak používat balíčky a rychlý úvod do terminologie umbraca]]> Umbraco %0% je nainstalováno a připraveno k použití soubor /web.config a upravit klíč AppSetting umbracoConfigurationStatus dole na hodnotu '%0%'.]]> - ihned začít kliknutím na tlačítko "Spustit Umbraco" níže.
Jestliže je pro Vás umbraco nové, + ihned začít kliknutím na tlačítko "Spustit Umbraco" níže.
Jestliže je pro Vás umbraco nové, spoustu zdrojů naleznete na naších stránkách "začínáme".]]>
Spustit Umbraco Chcete-li spravovat Váš web, jednoduše přejděte do administrace umbraca a začněte přidávat obsah, upravovat šablony a stylopisy, nebo přidávat nové funkce]]> @@ -583,13 +583,13 @@ ]]>
Hi %0%

-

Toto je automatická zpráva informující Vás, že úloha '%1%' +

Toto je automatická zpráva informující Vás, že úloha '%1%' byla provedena na stránce '%2%' uživatelem '%3%'

@@ -601,7 +601,7 @@

@@ -944,9 +944,6 @@ Oprávnění Uživatele Typy Uživatelů Uživatelé - - - Nová aktualizace je připrvena diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index b526f1b47b..2f31f2d24e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1276,9 +1276,6 @@ Mange hilsner fra Umbraco robotten Brugertilladelser Bruger Typer Brugere - - - Ny opdatering er klar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index cbeb8ab2c5..bdc459d596 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -483,7 +483,7 @@ Umbraco benötigt Schreibrechte auf verschiedene Verzeichnisse, um Dateien wie Bilder oder PDF-Dokumente speichern zu können. Außerdem werden temporäre Daten zur Leistungssteigerung der Website angelegt. Ich möchte mit einem leeren System ohne Inhalte und Vorgaben starten - Die Website ist zur Zeit komplett leer und ohne Inhalte und Vorgaben zu Erstellung eigener Dokumenttypen und Vorlagen bereit. + Die Website ist zur Zeit komplett leer und ohne Inhalte und Vorgaben zu Erstellung eigener Dokumenttypen und Vorlagen bereit. (<a href="http://Umbraco.tv/documentation/videos/for-site-builders/foundation/document-types">So geht's</a>) Sie können "Runway" auch jederzeit später installieren. Verwenden Sie hierzu den Punkt "Pakete" im Entwickler-Bereich. @@ -497,8 +497,8 @@ Dies sind unsere empfohlenen Module. Schauen Sie sich die an, die Sie installier Ich möchte mit einer einfache Website starten <p> -"Runway" ist eine einfache Website mit einfachen Dokumententypen und Vorlagen. Der Installer kann Runway automatisch einrichten, -aber es kann einfach verändert, erweitert oder entfernt werden. Es ist nicht zwingend notwendig und Umbraco kann auch ohne Runway verwendet werden. +"Runway" ist eine einfache Website mit einfachen Dokumententypen und Vorlagen. Der Installer kann Runway automatisch einrichten, +aber es kann einfach verändert, erweitert oder entfernt werden. Es ist nicht zwingend notwendig und Umbraco kann auch ohne Runway verwendet werden. Runway bietet eine einfache Basis zum schnellen Start mit Umbraco. Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die "Runway Modules" und Ihre Runway-Seite erweitern. </p> @@ -578,13 +578,13 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Ihr freundlicher Umbraco-Robot Hallo %0%

-

Dies ist eine automatisch E-Mail, welche Sie informiert, dass die Aufgabe '%1%' +

Dies ist eine automatisch E-Mail, welche Sie informiert, dass die Aufgabe '%1%' an der Seite '%2%' vom Benutzer '%3%' ausgeführt wurde.

@@ -596,7 +596,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die

@@ -927,9 +927,6 @@ Ihr freundlicher Umbraco-Robot Berechtigungen Benutzertypen Benutzer - - - Neues Update verfügbar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index 935f6f88b8..f1fb38215a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -410,7 +410,7 @@ añadir prevalor - Tipo de datos GUID Tipo de datos GUIDprestar control @@ -421,7 +421,7 @@ Mostrar etiqueta - Todos los tipos y datos de propiedad usar este tipo de datos lo borrará permanentemente, por favor confirma que quieres borrarlos también @@ -714,41 +714,41 @@ El enlace pulsado es inválido o ha caducado Umbraco: Restaurar contraseña - - - - - - + + + + + + + - - + +
+
- - - + + + -
- -
- -
+ +
+ +
-
+ - + - - - + + +
+

- - + + -
+
- - + +
+

Restauración de contraseña requerida

@@ -758,12 +758,12 @@

- - + + +
+
Pulsa este enlace para restaurar tu contraseña - -
@@ -775,22 +775,22 @@ %1% -

-
-
+ - -


-
- - + +


+
+ + - + ]]> @@ -916,7 +916,7 @@ Define una sección nombrada @section { ... }. Esto se puede mostrar en un área específica de la plantilla madre usando @RenderSection. ]]> Muestra una sección nombrada @@ -1300,13 +1300,13 @@ Campos Incluir subpáginas @@ -1355,9 +1355,6 @@ Hojas de estilo Plantillas Usuarios - - - Existe una nueva actualización @@ -1454,41 +1451,41 @@ Volver a usuarios Umbraco: Invitación - - - - - - + + + + + + + - - + +
+
- - - + + + -
- -
- -
+ +
+ +
-
+ - + - - - + + +
+

- - + + -
+
- - + +
+

Hi %0%,

@@ -1506,12 +1503,12 @@
- - + + +
+
Pulsa este enlace para aceptar la invitación - -
@@ -1526,22 +1523,22 @@ %3% -

-
-
+ - -


-
- - + +


+
+ + - + ]]> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index ee269ff67b..d8642303fb 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -89,7 +89,7 @@ Domaine '%0%' mis à jour Editer les domaines actuels
Les domaines contenant un chemin d'un niveau sont autorisés, ex : "example.com/en". Pour autant, cela + "https://www.example.com/".

Les domaines contenant un chemin d'un niveau sont autorisés, ex : "example.com/en". Pour autant, cela devrait être évité. Utilisez plutôt la gestion de la culture et des noms d'hôte.]]>
Hériter Culture @@ -673,9 +673,9 @@ Appuyez sur le bouton Upgrader pour mettre à jour votre base de données vers Umbraco %0%

N'ayez pas d'inquiétude : aucun contenu ne sera supprimé et tout continuera à fonctionner parfaitement par après ! -

+

]]> - Appuyez sur Suivant pour + Appuyez sur Suivant pour poursuivre. ]]> Suivant pour poursuivre la configuration]]> Le mot de passe par défaut doit être modifié !]]> @@ -725,7 +725,7 @@ "Runway" est un site simple qui fournit des types de documents et des modèles de base. L'installateur peut mettre en place Runway automatiquement pour vous, - mais vous pouvez facilement l'éditer, l'enrichir, ou le supprimer par la suite. Il n'est pas nécessaire, et vous pouvez parfaitement vous en passer pour utiliser Umbraco. Cela étant dit, + mais vous pouvez facilement l'éditer, l'enrichir, ou le supprimer par la suite. Il n'est pas nécessaire, et vous pouvez parfaitement vous en passer pour utiliser Umbraco. Cela étant dit, Runway offre une base facile, fondée sur des bonnes pratiques, pour vous permettre de commencer plus rapidement que jamais. Si vous choisissez d'installer Runway, vous pouvez sélectionner en option des blocs de base, appelés Runway Modules, pour enrichir les pages de votre site.

@@ -746,7 +746,7 @@ Vous avez installé Runway, alors pourquoi ne pas jeter un oeil au look de votre Aide et informations complémentaires Obtenez de l'aide de notre communauté "award winning", parcourez la documentation ou regardez quelques vidéos gratuites sur la manière de construire un site simple, d'utiliser les packages ainsi qu'un guide rapide sur la terminologie Umbraco]]> Umbraco %0% est installé et prêt à être utilisé - fichier /web.config et mettre à jour le paramètre AppSetting umbracoConfigurationStatus situé en bas à la valeur '%0%'.]]> démarrer instantanément en cliquant sur le bouton "Lancer Umbraco" ci-dessous.
Si vous débutez avec Umbraco, vous pouvez trouver une foule de ressources dans nos pages "Getting Started".]]>
@@ -790,41 +790,41 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Le lien sur lequel vous avez cliqué est non valide ou a expiré. Umbraco: Ré-initialiser le mot de passe - - - - - - + + + + + + + - - + +
+
- - - + + + -
- -
- -
+ +
+ +
-
+ - + - - - + + +
+

- - + + -
+
- - + +
+

Une réinitialisation de votre mot de passe a été demandée

@@ -834,12 +834,12 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à

- - + + +
+
Cliquez sur ce lien pour réinitialiser votre mot de passe - -
@@ -851,22 +851,22 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à %1% -

-
-
+ - -


-
- - + +


+
+ + - + ]]> @@ -905,54 +905,54 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Avec les salutations du Robot Umbraco ]]> - - - - - - - + + + + + + + - - + +
+
- - + + -
- -
- -
+ +
+ +
-
+ - + - - - + + -
+

- - + - +
+
- - +
+

Salut %0%,

-

+

Ceci est un email automatique pour vous informer que la tâche '%1%' a été exécutée sur la page '%2%' par l'utilisateur '%3%'

- - @@ -962,7 +962,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à

Résumé de la mise à jour :

+ +
MODIFIER
%6% -
+

Bonne journée !

@@ -974,10 +974,10 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à

-


+


@@ -1549,9 +1549,6 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Modèles Utilisateurs Analytique - - - Nouvelle mise à jour disponible @@ -1649,41 +1646,41 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Retour aux utilisateurs Umbraco: Invitation - - - - - - + + + + + + + - - + +
+
- - - + + + -
- -
- -
+ +
+ +
-
+
+ - - - + + +
+

- - + + -
+
- - + +
+

Salut %0%,

@@ -1701,12 +1698,12 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à
- - + + +
+
Cliquez sur ce lien pour accepter l'invitation - -
@@ -1721,22 +1718,22 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à %3% -

-
-
+ - -


-
- - + +


+
+ + - + ]]> Inviter diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml index 4945fcc93a..0fb00ef139 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml @@ -375,7 +375,7 @@ Database not found! Please check that the information in the "connection string" of the “web.config” file is correct.

To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

- Click the retry button when + Click the retry button when done.
More information on editing web.config here.

]]>
@@ -386,9 +386,9 @@ Press the upgrade button to upgrade your database to Umbraco %0%

Don't worry - no content will be deleted and everything will continue working afterwards! -

+

]]>
- Press Next to + Press Next to proceed. ]]> next to continue the configuration wizard]]> The Default users’ password needs to be changed!]]> @@ -423,7 +423,7 @@ ]]> אני רוצה להתחיל מאתר ריק learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ]]> @@ -437,8 +437,8 @@ ברצוני להתחיל עם אתר פשוט - "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, - but you can easily edit, extend or remove it. It’s not necessary and you can perfectly use Umbraco without it. However, + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, + but you can easily edit, extend or remove it. It’s not necessary and you can perfectly use Umbraco without it. However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.

@@ -459,9 +459,9 @@ You installed Runway, so why not see how your new website looks.]]>
Further help and information Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> אומברקו %0% מותקנת ומוכנה לשימוש - /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> - started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, + started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, you can find plenty of resources on our getting started pages.]]>
Launch Umbraco To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> @@ -515,13 +515,13 @@ To manage your website, simply open the Umbraco back office and start adding con ]]>
Hi %0%

-

This is an automated mail to inform you that the task '%1%' +

This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%'

@@ -533,7 +533,7 @@ To manage your website, simply open the Umbraco back office and start adding con

@@ -801,9 +801,9 @@ To manage your website, simply open the Umbraco back office and start adding con באפשרותך גם להיכנס למערכת על מנת לראות את כל משימות התירגום http://%3% - + המשך יום נעים. - + ]]>
לא נמצאו משתמשמים המוגדרים כמתרגמים. יש ליצור משתמש המוגדר כמתרגם לפני שליחת תוכן לתירגום העמוד '%0%' נשלח לתירגום @@ -847,9 +847,6 @@ To manage your website, simply open the Umbraco back office and start adding con הרשאות משתמש משתמש מקליד משתמש - - - עידכון חדש זמין diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml index 6132eb0c5d..7b8da34c7a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml @@ -411,8 +411,8 @@ Vorrei iniziare da un sito semplice - "Runway" è un semplice sito web contenente alcuni tipi di documento e alcuni templates di base. L'installer configurerà Runway per te automaticamente, - ma tu potrai facilmente modificarlo, estenderlo o eliminarlo. Non è necessario installarlo e potrai usare Umbraco anche senza di esso, ma + "Runway" è un semplice sito web contenente alcuni tipi di documento e alcuni templates di base. L'installer configurerà Runway per te automaticamente, + ma tu potrai facilmente modificarlo, estenderlo o eliminarlo. Non è necessario installarlo e potrai usare Umbraco anche senza di esso, ma Runway ti offre le basi e le best practices per cominciare velocemente. Se sceglierai di installare Runway, volendo potrai anche selezionare i moduli di Runway per migliorare le pagine.

@@ -491,13 +491,13 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i ]]>
Salve %0%

-

Questa è un'email automatica per informare che l'azione '%1%' +

Questa è un'email automatica per informare che l'azione '%1%' è stata eseguita sulla pagina '%2%' dall'utente '%3%'

@@ -509,7 +509,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i

@@ -832,9 +832,6 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Permessi Utente Tipi di Utente Utenti - - - diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index 6d6ca29748..4b9bda319e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -1073,9 +1073,6 @@ Runwayをインストールして作られた新しいウェブサイトがど ユーザーの権限 ユーザータイプ ユーザー - - - 新しい更新があります diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml index 63654fc0f5..f2d27094a8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml @@ -408,7 +408,7 @@ Umbraco 는 특정 디렉토리에 쓰기/수정 권한이 필요합니다. 이것은 PDF나 그림과 같은 파일을 저장하고 cache같은 임시데이터을 위해 사용됩니다. scratch를 시작하기 원합니다. learn how) Runway설치를 나중에 실행하실 수 있습니다. 개발도구 부분에서 패키지를 선택하세요. ]]> @@ -445,7 +445,7 @@ 우수 커뮤니티에서 도음을 받으세요. 간단한 사이트제작이나 패키지 사용법, Umbraco기술의 퀵가이드를 제공하는 문서를 보시거나 무료 비디오를 시청하세요.]]>
Umbraco %0% 가 설치되어 사용준비가 되었습니다. /web.config file을 수동으로 편집해야 합니다. AppSetting 키의 UmbracoConfigurationStatus'%0%'의 값으로 설정하세요.]]> - Umbraco 와 첫만남이시면
아래의 "Umbraco 접속하기" 버튼을 클릭하여 즉시 시작하실 수 있습니다. + Umbraco 와 첫만남이시면
아래의 "Umbraco 접속하기" 버튼을 클릭하여 즉시 시작하실 수 있습니다. 시작페이지에서 풍부한 리소소를 제공받을 수 있습니다.]]>
Umbraco 실행 사이트 관리를 위해서 Umbraco 관리자를 여시고 컨텐츠를 추가하시거나 템플릿과 스타일시트 업데이트 또는 새기능을 추가하세요]]> @@ -492,21 +492,21 @@ 사용자 '%3%' 가 작업 '%1%' 를 페이지 '%2%' 에서 진행했음을 알리는 자동 발송 메일입니다. - + 편집하시려면 http://%4%/#/content/content/edit/%5% 로 이동하세요 좋은 하루 되세요! - + ]]>
안녕하세요 %0%

-

사용자 '%3%' 가 작업 '%1%' 를 +

사용자 '%3%' 가 작업 '%1%' 를 페이지 '%2%' 에서 진행했음을 알리는 자동 발송 메일입니다.

@@ -518,11 +518,11 @@

-

좋은 하루 되세요!

+

좋은 하루 되세요!

]]>
%1%에 대한 [%0]알림이 %2%에 생성되었습니다 알림 @@ -779,8 +779,8 @@ %2% 에 의해 문서 '%1%' 가 '%5%' 로 번역요청되었음을 알리는 자동 발송 메일입니다. - 편집하시려면 http://%3%/translation/details.aspx?id=%4% 로 - + 편집하시려면 http://%3%/translation/details.aspx?id=%4% 로 + 번역작업을 전반적으로 보시려면 Umbraco에 로그인 하세요 http://%3% @@ -827,9 +827,6 @@ 사용자권한 사용자 유형 사용자 - - - 새 업데이트가 준비되었습니다. diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 58f99a3086..1f3d59bc79 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -912,9 +912,6 @@ Vennlig hilsen Umbraco roboten Brukertillatelser Brukertyper typer Brukere - - - Ny oppdatering er klar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index db737379ff..282f80473b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -285,7 +285,7 @@ Aantal regels Plaats een placeholder id door een ID op uw placeholder te zetten kunt u inhoud plaatsen in deze template vanuit onderliggende templates, door te referreren naar deze ID door gebruik te maken van een <asp:content /> element.]]> - Selecteer een placeholder id uit onderstaande lijst. U kunt alleen + Selecteer een placeholder id uit onderstaande lijst. U kunt alleen Id's kiezen van de master van de huidige template..]]> Klik op de afbeelding voor volledige grootte Kies een item @@ -579,7 +579,7 @@ Ik wil met een eenvoudige website beginnen "Runway" is een eenvoudige website die je van enkele elementaire documenttypes en templates voorziet. De installer kan Runway automatisch voor je opzetten, maar je kunt het gemakkelijk aanpassen, uitbreiden of verwijderen. Het is niet vereist en je kunt Umbraco prima zonder Runway gebruiken. -Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je er voor kiest om Runway te installeren, dan kun je optioneel de bouwstenen (genaamd Runway Modules) kiezen om je Runway pagina's te verbeteren.

Runway omvat: Home pagina, Getting Started pagina, Module installatie pagina.
Optionele Modules: Top Navigatie, Sitemap, Contact, Gallery.
+Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je er voor kiest om Runway te installeren, dan kun je optioneel de bouwstenen (genaamd Runway Modules) kiezen om je Runway pagina's te verbeteren.

Runway omvat: Home pagina, Getting Started pagina, Module installatie pagina.
Optionele Modules: Top Navigatie, Sitemap, Contact, Gallery.
]]>
Wat is Runway Stap 1/5: Licentie aanvaarden @@ -662,17 +662,17 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Een prettige dag! Dit is een bericht van uw Content Management Systeem. - + ]]>
Hi %0%

-

Dit is een geautomatiseerde mail om u op de hoogte te brengen dat de taak '%1%' - is uitgevoerd op pagina '%2%' +

Dit is een geautomatiseerde mail om u op de hoogte te brengen dat de taak '%1%' + is uitgevoerd op pagina '%2%' door gebruiker '%3%'

@@ -684,7 +684,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je

@@ -770,7 +770,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je %0% kan niet worden gepubliceerd, omdat de eigenschappen:%1% de validatieregels niet hebben doorstaan. ]]>
Geen vertaal-gebruikers gevonden. Maak eerst een vertaal-gebruiker aan voordat je pagina's voor vertaling verstuurd De pagina '%0%' is verstuurd voor vertaling @@ -1122,9 +1122,6 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Scripts Stylesheets Sjablonen - - - Nieuwe update beschikbaar @@ -1222,41 +1219,41 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Terug naar gebruikers Umbraco: Uitnodiging - - - - - - + + + + + + + - - + +
+
- - - + + + -
- -
- -
+ +
+ +
-
+ - + - - - + + +
+

- - + + -
+
- - + +
+

Hallo %0%,

@@ -1274,12 +1271,12 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
- - + + +
+
Klik hier om de uitnodiging te accepteren - -
@@ -1294,22 +1291,22 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je %3% -

-
-
+ - -


-
- - + +


+
+ + - + ]]> Uitnodigen diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index e108887221..f7125e437c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -87,7 +87,7 @@ Domena '%0%' została zaktualizowana Edytuj Aktualne Domeny Odziedziczona Język @@ -335,9 +335,9 @@ Cache strony zostanie odświeżone. Cała zawartość opublikowana będzie aktualna, lecz nieopublikowana zawartość pozostanie niewidoczna Liczba kolumn Liczba wierszy - Ustaw zastępczy ID Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów, + Ustaw zastępczy ID Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów, ustawiając dowiązanie do tego ID na elemencie <asp:treści />]]> - Wybierz zastępczy ID z poniższej listy. Możesz wybierać tylko + Wybierz zastępczy ID z poniższej listy. Możesz wybierać tylko spośród ID na szablonie nadrzędnym tego formularza.]]> Kliknij na obrazie, aby zobaczyć go w pełnym rozmiarze Wybierz element @@ -626,23 +626,23 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umbraco %0% ]]> Dalej, aby kontynuować.]]> - Nie odnaleziono bazy danych! Sprawdź, czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.

-

Aby kontynuować, dokonaj edycji pliku "web.config" (używając Visual Studio lub dowolnego edytora tekstu), przemieść kursor na koniec pliku, dodaj parametry połączenia do Twojej bazy danych w kluczu o nazwie "umbracoDbDSN" i zapisz plik.

+ Nie odnaleziono bazy danych! Sprawdź, czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.

+

Aby kontynuować, dokonaj edycji pliku "web.config" (używając Visual Studio lub dowolnego edytora tekstu), przemieść kursor na koniec pliku, dodaj parametry połączenia do Twojej bazy danych w kluczu o nazwie "umbracoDbDSN" i zapisz plik.

- Kliknij ponów próbę kiedy + Kliknij ponów próbę kiedy skończysz.
Tu znajdziesz więcej informacji na temat edycji pliku "web.config".

]]>
- Skontaktuj się z Twoim dostawą usług internetowych jeśli zajdzie taka potrzeba. + Skontaktuj się z Twoim dostawą usług internetowych jeśli zajdzie taka potrzeba. W przypadku instalacji na lokalnej maszynie lub serwerze możesz potrzebować pomocy administratora.]]> - Naciśnij przycisk aktualizuj, aby zaktualizować swoją bazę danych do Umbraco %0%

+ Naciśnij przycisk aktualizuj, aby zaktualizować swoją bazę danych do Umbraco %0%

Bez obaw - żadne dane nie zostaną usunięte i wszystko będzie działać jak należy!

]]>
- Naciśnij przycisk Dalej, aby + Naciśnij przycisk Dalej, aby kontynuować.]]> Dalej, aby kontynuować kreatora instalacji.]]> Hasło domyślnego użytkownika musi zostać zmienione!]]> @@ -655,7 +655,7 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Zmienione pliki i foldery Więcej informacji na temat ustalania pozwoleń dla Umbraco znajdziesz tutaj Musisz zezwolić procesowi ASP.NET na zmianę poniższych plików/folderów - Twoje ustawienia uprawnień są prawie idealne!

+ Twoje ustawienia uprawnień są prawie idealne!

Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane, aby w pełni wykorzystać możliwości Umbraco.]]>
Jak to Rozwiązać Kliknij tutaj, aby przeczytać wersję tekstową @@ -663,42 +663,42 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Twoje ustawienia uprawnień mogą stanowić problem!

Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane, aby w pełni wykorzystać możliwości Umbraco.]]>
- Twoje ustawienia uprawnień nie są gotowe na Umbraco! -

+ Twoje ustawienia uprawnień nie są gotowe na Umbraco! +

Aby Umbraco mogło działać musisz uaktualnić swoje ustawienia zabezpieczeń.]]>
- Twoje ustawienia uprawnień są idealne!

+ Twoje ustawienia uprawnień są idealne!

Umbraco będzie działać bez problemów i będzie można instalować pakiety!]]>
Rozwiązywanie problemów z folderami Kliknij ten link, aby uzyskać więcej informacji na temat problemów z ASP.NET i tworzeniem folderów. Ustawianie uprawnień dostępu do folderów Chcę zacząć od zera dowiedz się jak) + Twoja strona jest obecnie pusta. To idealna sytuacja, jeśli chcesz zacząć od zera i stworzyć własne typy dokumentów i szablony. + (dowiedz się jak) Ciągle możesz wybrać, aby zainstalować Runway w późniejszym terminie. W tym celu przejdź do sekcji Deweloper i wybierz Pakiety. ]]> Właśnie stworzyłeś czystą instalację platformy Umbraco. Co chcesz zrobić teraz? Pakiet Runway został zainstalowany pomyślnie - To jest nasza lista rekomendowanych modułów. Zaznacz te, które chcesz zainstalować lub wyświetl pełną listę modułów + Twoje fundamenty są postawione właściwie. Wybierz, które moduły chcesz na nich zainstalować.
+ To jest nasza lista rekomendowanych modułów. Zaznacz te, które chcesz zainstalować lub wyświetl pełną listę modułów ]]>
Rekomendowane tylko dla doświadczonych użytkowników Chcę rozpocząć z prostą stroną - Pakiet "Runway" to prosta strona, dostarczająca kilku podstawowych typów dokumentów i szablonów. Instalator może automatycznie zainstalować pakiet Runway za Ciebie, - ale możesz w łatwy sposób edytować, rozszerzyć lub usunąć go. Nie jest on potrzebny i możesz doskonale używać Umbraco bez niego. - Jednakże pakiet Runway oferuje łatwą podstawę, bazującą na najlepszych praktykach, która pozwolić Ci rozpocząć pracę w mgnieniu oka. - Jeśli zdecydujesz się zainstalować pakiet Runway, możesz opcjonalnie wybrać podstawowe klocki zwane Modułami Runway, aby poprawić swoje strony. -

+ Pakiet "Runway" to prosta strona, dostarczająca kilku podstawowych typów dokumentów i szablonów. Instalator może automatycznie zainstalować pakiet Runway za Ciebie, + ale możesz w łatwy sposób edytować, rozszerzyć lub usunąć go. Nie jest on potrzebny i możesz doskonale używać Umbraco bez niego. + Jednakże pakiet Runway oferuje łatwą podstawę, bazującą na najlepszych praktykach, która pozwolić Ci rozpocząć pracę w mgnieniu oka. + Jeśli zdecydujesz się zainstalować pakiet Runway, możesz opcjonalnie wybrać podstawowe klocki zwane Modułami Runway, aby poprawić swoje strony. +

- Dołączone z pakietem Runway: Strona domowa, strona Jak rozpocząć pracę, strona Instalowanie Modułów.
- Opcjonalne moduły:Górna nawigacja, Mapa strony, Formularz kontaktowy, Galeria. + Dołączone z pakietem Runway: Strona domowa, strona Jak rozpocząć pracę, strona Instalowanie Modułów.
+ Opcjonalne moduły:Górna nawigacja, Mapa strony, Formularz kontaktowy, Galeria.
]]>
Co to jest pakiet Runway @@ -708,23 +708,23 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Krok 4/5: Sprawdzanie zabezpieczeń Umbraco Krok 5/5: Umbraco jest gotowe do pracy Dziękujemy za wybór Umbraco - Przeglądaj swoją nową stronę + Przeglądaj swoją nową stronę Pakiet Runway został zainstalowany, zobacz zatem jak wygląda Twoja nowa strona.]]> - Dalsza pomoc i informacje + Dalsza pomoc i informacje Zaczerpnij pomocy z naszej nagrodzonej społeczności, przeglądaj dokumentację lub obejrzyj niektóre darmowe filmy o tym, jak budować proste strony, jak używać pakietów i szybki przewodnik po terminologii Umbraco]]> Umbraco %0% zostało zainstalowane i jest gotowe do użycia - plik web.config i zaktualizować klucz AppSetting o nazwie UmbracoConfigurationStatus na dole do wartości '%0%'.]]> - rozpocząć natychmiast klikając przycisk "Uruchom Umbraco" poniżej.
Jeżeli jesteś nowy dla Umbraco + rozpocząć natychmiast klikając przycisk "Uruchom Umbraco" poniżej.
Jeżeli jesteś nowy dla Umbraco znajdziesz mnóstwo materiałów na naszych stronach "jak rozpocząć".]]>
- Uruchom Umbraco + Uruchom Umbraco Aby zarządzać swoją stroną po prostu otwórz zaplecze Umbraco i zacznij dodawać treść, aktualizować szablony i style lub dodawaj nową funkcjonalność]]> Połączenie z bazą danych nie zostało ustanowione. Umbraco wersja 3 Umbraco wersja 4 Zobacz - Umbraco %0% dla świeżej instalacji lub aktualizacji z wersji 3.0. -

+ Umbraco %0% dla świeżej instalacji lub aktualizacji z wersji 3.0. +

Wciśnij "dalej", aby rozpocząć proces konfigruacji.]]>
@@ -778,11 +778,11 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Edytuj powiadomienie dla %0% instaluj, aby zainstalować bazę danych Umb Pozdrowienia od robota Umbraco ]]> - Witaj %0%

+ Witaj %0%

-

To jest automatyczny e-mail, wysłany, aby poinformować Cię, że polecenie '%1%' - zostało wykonane na stronie '%2%' - przez użytkownika '%3%' -

-
+

To jest automatyczny e-mail, wysłany, aby poinformować Cię, że polecenie '%1%' + zostało wykonane na stronie '%2%' + przez użytkownika '%3%' +

+ -

-

Podsumowanie zmian:

- - %6% -
-

+      EDYTUJ        +
+
+

+

Podsumowanie zmian:

+ + %6% +
+

- + -

Miłego dnia!

- Pozdrowienia od robota Umbraco +

Miłego dnia!

+ Pozdrowienia od robota Umbraco

]]>
[%0%] Powiadomienie o %1% wykonane na %2% Powiadomienie + Wskaż pakiet z Twojego komputera, poprzez kliknięcie na przycisk "Przeglądaj"
i wskaż gdzie jest zapisany. Pakiety Umbraco przeważnie posiadają rozszerzenie ".umb" lub ".zip". ]]>
Upuść, aby załadować @@ -875,7 +875,7 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Pakiet został pomyślnie odinstalowany Odinstaluj pakiet - Uwaga: wszystkie elementy, media, itp. w zależności od elementów, które usuwasz, przestaną działać i mogą spowodować niestabilność systemu, + Uwaga: wszystkie elementy, media, itp. w zależności od elementów, które usuwasz, przestaną działać i mogą spowodować niestabilność systemu, więc odinstalowuj z uwagą. W przypadku problemów skontaktuj się z autorem pakietu.]]> Pobierz aktualizację z repozytorium Aktualizuj pakiet @@ -941,7 +941,7 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb %0% został opublikowany %0% oraz podstrony zostały opublikowane Publikuj %0% ze wszytkimi podstronami - OK , aby publikować % 0% i spowodować upublicznienie całej zawartości.

+ OK , aby publikować % 0% i spowodować upublicznienie całej zawartości.

Możesz opublikować tą stronę wraz ze wszystkimi podstronami zaznaczając poniżej publikuj wszystkie węzły pochodne ]]>
@@ -1283,15 +1283,15 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb @@ -1337,9 +1337,6 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Szablony Częściowe Widoki Pliki Makro Częściowych Widoków - - - Aktualizacja jest gotowa diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml index 9977136445..98f557a78c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml @@ -373,8 +373,8 @@ Pressione o botão atualizar para atualizar seu banco de dados para Umbraco %0%

- Não se preocupe - nenhum conteúdo será removido e tudo estará funcionando depois disto!

- + Não se preocupe - nenhum conteúdo será removido e tudo estará funcionando depois disto!

+ ]]>
Pressione Próximo para prosseguir.]]> próximo
para continuar com o assistente de configuração]]>
@@ -403,7 +403,7 @@ Para correr Umbraco você vai precisar atualizar as configurações de permissõ Resolvendo problemas de pastas Siga este link para mais informações sobre problemas com ASP.NET e criação de pastas Configurando permissões de pastas - Eu quero começar do zero Incluso com Runway: Página Inicial, Começando, Instalando Módulos.
Módulos Opcionais: Navegação de Topo, Mapa de Site, Contato, Galeria. - + ]]>
O que é Runway Passo 1/5 Aceitar Licença @@ -491,13 +491,13 @@ Vá até http://%4%/#/content/content/edit/%5% para editar. Saudações do robô Umbraco]]>
Olá %0%

-

Esta é uma mensagem automatizada para informar que a tarefa '%1%' +

Esta é uma mensagem automatizada para informar que a tarefa '%1%' foi completada na página '%2%' pelo usuário '%3%'

@@ -509,7 +509,7 @@ Vá até http://%4%/#/content/content/edit/%5% para editar.

@@ -816,9 +816,6 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar pub Permissões de usuário Tipos de Usuários Usuários - - - Nova atualização pronta diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 3b09c34818..7fe4433e38 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -930,41 +930,41 @@ Ссылка, по которой Вы попали сюда, неверна или устарела Umbraco: сброс пароля - - - - - - + + + + + + + - - + +
+
- - - + + + -
- -
- -
+ +
+ +
-
+ - + - - - + + +
+

- - + + -
+
- - + +
+

Запрошен сброс пароля

@@ -974,12 +974,12 @@

- - + + +
+
Нажмите на эту ссылку для того, чтобы сбросить пароль - -
@@ -991,22 +991,22 @@ %1% -

-
-
+ - -


-
- - + +


+
+ + - + ]]> @@ -1068,53 +1068,53 @@ Генератор уведомлений Umbraco. ]]> - - - - - - + + + + + + + - - + +
+
- - + + -
- -
- -
+ +
+ +
-
+ - + - - - + + -
+

- - + - +
+
- - +
+

Здравствуйте, %0%,

-

+

Это автоматически сгенерированное сообщение, отправленное, чтобы уведомить Вас о том, что операция '%1%' была выполнена на странице '%2%' пользователем '%3%'

- - @@ -1124,7 +1124,7 @@

Обзор обновления:

+ +
ВНЕСТИ ИЗМЕНЕНИЯ
%6% -
+

Удачного дня!

@@ -1136,10 +1136,10 @@

-


+


@@ -1668,9 +1668,6 @@ Стили CSS Шаблоны Пользователи - - - Доступны обновления @@ -1706,41 +1703,41 @@ Пригласить Приглашение в панель администрирования Umbraco

Здравствуйте, %0%,

Вы были приглашены пользователем %1%, и Вам предоставлен доступ в панель администрирования Umbraco.

Сообщение от %1%: %2%

Перейдите по этой ссылке, чтобы принять приглашение.

Если Вы не имеете возможности перейти по ссылке, скопируйте нижеследующий текст ссылки и вставьте в адресную строку Вашего браузера.

%3%

]]> - - - - - - + + + + + + + - - + +
+
- - - + + + -
- -
- -
+ +
+ +
-
+
+ - - - + + +
+

- - + + -
+
- - + +
+

Здравствуйте, %0%,

@@ -1758,12 +1755,12 @@
- - + + +
+
Нажмите на эту ссылку, чтобы принять приглашение - -
@@ -1778,22 +1775,22 @@ %3% -

-
-
+ - -


-
- - + +


+
+ + - + ]]> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index 5434f6bb47..f16129b315 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -856,9 +856,6 @@ Användarrättigheter Användartyper Användare - - - Ny uppdatering tillgänglig diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index bd39a6fba8..41640faa4a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -55,7 +55,7 @@ etki /> bir düzey yollar desteklenir
Miras Al Kültür @@ -421,7 +421,7 @@ Veritabanı bulunamadı! "Web.config" nin "bağlantı dizesinde" bilgi dosyası doğru olup olmadığını kontrol edin.

Devam etmek için, (Visual Studio veya sevdiğiniz metin editörü kullanarak) "web.config" dosyasını düzenlemek lütfen, altına gidin "UmbracoDbDSN" adlı anahtarı veritabanınız için bağlantı dizesini eklemek ve dosyayı kaydedin.

- Tekrar dene. + Tekrar dene.
Burada düzenleme web.config Hakkında Daha Fazla Bilgi.

]]>
@@ -434,7 +434,7 @@

Merak etmeyin - hiçbir içerik silinmeyecek ve her şey sonradan çalışmaya devam edecektir! -

+

]]> Sonraki işlem. ]]> next
to continue the configuration wizard]]> @@ -470,7 +470,7 @@ ]]> Baştan başlamak istiyorum learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ]]> @@ -484,8 +484,8 @@ I want to start with a simple website - "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, - but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, + but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.

@@ -506,9 +506,9 @@ You installed Runway, so why not see how your new website looks.]]>
Further help and information Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> Umbraco %0% is installed and ready for use - /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> - started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, + started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, you can find plenty of resources on our getting started pages.]]>
Launch Umbraco To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> @@ -575,13 +575,13 @@ To manage your website, simply open the Umbraco back office and start adding con ]]>
Hi %0%

-

This is an automated mail to inform you that the task '%1%' +

This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%'

@@ -593,7 +593,7 @@ To manage your website, simply open the Umbraco back office and start adding con

@@ -936,9 +936,6 @@ To manage your website, simply open the Umbraco back office and start adding con Scriptler Stil dosyaları Şablonlar - - - Yeni bir güncelleme geldi diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index e6d013e4d1..3945075d29 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -60,7 +60,7 @@ 只需要设置语言部分即可。]]>
继承 语言 - + 也可以从父节点继承。]]> 域名 @@ -543,7 +543,7 @@ 点击更新来更新系统到 %0%

不用担心更新会丢失数据! -

+

]]>
点击下一步继续。]]> 下一步继续]]> @@ -615,7 +615,7 @@ 更多的帮助信息 从社区获取帮助]]> 系统 %0% 安装完毕 - /web.config file 的 AppSetting 键 + /web.config file 的 AppSetting 键 UmbracoConfigurationStatus'%0%'。]]> 立即开始请点“运行系统”
如果您是新手, 您可以得到相当丰富的学习资源。]]>
运行系统 @@ -700,7 +700,7 @@

@@ -712,7 +712,7 @@

@@ -1073,7 +1073,7 @@ %0%: 您好!这是一封自动邮件来提醒您注意,%2%的文档'%1%' - 需要您翻译为'%5%' + 需要您翻译为'%5%' 转到 http://%3%/translation/details.aspx?id=%4% 进行编辑 @@ -1128,9 +1128,6 @@ Users 分部视图 分部视图宏文件 - - - 有可用更新 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml index 9ca2278182..03282f6781 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml @@ -1108,9 +1108,6 @@ 腳本 樣式表 範本 - - - 有可用更新 From 6dd9bf56be3c068711333908256a1bbde437a1ea Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 09:23:42 +0100 Subject: [PATCH 087/585] Update logic for checking for groups to be more robust (as we could still have one named group) --- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 7fedc44a15..1a0db84805 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -92,7 +92,8 @@ namespace Umbraco.Web.Trees } //Don't apply fancy grouping logic futher down, if we only have one group of items - if (CoreTrees.Value.Count() == 1) + var hasGroups = CoreTrees.Value.Any(x => x.Key != null); + if (hasGroups == false) { var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); multiTree.Name = Services.TextService.Localize("sections/" + application); From f996653a6758baa8bf01ddec9f2974d38e4a7e04 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 11 Oct 2018 10:35:23 +0200 Subject: [PATCH 088/585] changes logic to Count --- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 1a0db84805..7e73f44267 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -27,8 +27,8 @@ namespace Umbraco.Web.Trees /// Attribute with a 'TreeGroup' property set /// This allows the settings section trees to be grouped by Settings, Templating & Other ///
- private static readonly Lazy>> CoreTrees - = new Lazy>>(() => + private static readonly Lazy>> CoreTrees + = new Lazy>>(() => Current.Services.ApplicationTreeService.GetAllTypes() .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute(false)?.TreeGroup)) .GroupBy(x => x.TreeGroup) @@ -92,7 +92,7 @@ namespace Umbraco.Web.Trees } //Don't apply fancy grouping logic futher down, if we only have one group of items - var hasGroups = CoreTrees.Value.Any(x => x.Key != null); + var hasGroups = CoreTrees.Value.Count > 0; if (hasGroups == false) { var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); From 00bad829c3d22dd831e2437e5c12992f8c035768 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 11 Oct 2018 11:54:28 +0200 Subject: [PATCH 089/585] Removed rogue colon --- .../Persistence/Repositories/Implement/DocumentRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 12aed212fc..016e7d4f02 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -721,7 +721,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : filterClause.Item1; filterSql.Append( - where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)," : $"AND ({where})", + where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})", filterClause.Item2); } } From a73e2888468e7f6acecca267b7164e8ac7bf91f8 Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 11 Oct 2018 12:52:57 +0200 Subject: [PATCH 090/585] Few fixes - including completing a transaction, disabling the notifications so they can be handled manually. --- src/Umbraco.Core/Services/Implement/ContentService.cs | 2 ++ .../common/directives/components/content/edit.controller.js | 3 ++- .../src/views/content/overlays/sendtopublish.controller.js | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index a849813b13..48284d8138 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1825,6 +1825,8 @@ namespace Umbraco.Core.Services.Implement sendToPublishEventArgs.CanCancel = false; scope.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + + scope.Complete(); } return true; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index f452e6b812..c0f0c9724c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -473,7 +473,8 @@ //we need to return this promise so that the dialog can handle the result and wire up the validation response return performSave({ saveMethod: contentResource.sendToPublish, - action: "sendToPublish" + action: "sendToPublish", + showNotifications: false }).then(function (data) { //show all notifications manually here since we disabled showing them automatically in the save method formHelper.showNotifications(data); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js index d608ed7f27..c95860cdec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js @@ -69,10 +69,9 @@ return (variant.state === "Published" && !variant.isDirty && !variant.active || variant.state === "NotCreated" && !variant.isDirty && !variant.active); } - //when this dialog is closed, reset all 'sendToPublish' flags + //when this dialog is closed, reset all 'save' flags $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { - vm.variants[i].sendToPublish = false; vm.variants[i].save = false; } }); From 79c363c9009f5870e11c4ef927d2872866971c65 Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 11 Oct 2018 12:54:06 +0200 Subject: [PATCH 091/585] Updating the admin user id to the correct userId --- src/Umbraco.Core/Services/Implement/NotificationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index 3afb7c3777..cc76374715 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -72,7 +72,7 @@ namespace Umbraco.Core.Services.Implement // users being (dis)approved = not an issue, filtered in memory not in SQL // users being modified or created = not an issue, ordering by ID, as long as we don't *insert* low IDs // users being deleted = not an issue for GetNextUsers - var id = 0; + var id = Constants.Security.SuperUserId; var nodeIds = content.Path.Split(',').Select(int.Parse).ToArray(); const int pagesz = 400; // load batches of 400 users do From e1c7d23c81eabe4e9a6253368d78ebad148a1743 Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 11 Oct 2018 12:54:32 +0200 Subject: [PATCH 092/585] Adding a missing parameter --- .../src/common/resources/content.resource.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 721cd4da57..61dfe918c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -756,11 +756,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the saved content item. * */ - sendToPublish: function (content, isNew, files) { + sendToPublish: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", "PostSave"); - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, /** From 140e410dcca6602feb0b445e5b300eb191c063c9 Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 11 Oct 2018 14:40:18 +0200 Subject: [PATCH 093/585] Revert changes --- src/Umbraco.Web.UI/web.Template.config | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 943570376f..844855f10f 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -82,9 +82,8 @@ - - - + + From fe906f0e81fd0a27d170127afa67e958553d7585 Mon Sep 17 00:00:00 2001 From: Mike Masey Date: Sat, 13 Oct 2018 17:59:43 +0100 Subject: [PATCH 094/585] Update lazyload.js reference on install pages. --- src/Umbraco.Web.UI.Client/src/index.html | 2 +- src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml | 2 +- src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml | 2 +- src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html index bd354efc90..e15cf0ab62 100644 --- a/src/Umbraco.Web.UI.Client/src/index.html +++ b/src/Umbraco.Web.UI.Client/src/index.html @@ -21,7 +21,7 @@ - + diff --git a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml index 67adeaf84a..79968939ec 100644 --- a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml @@ -70,7 +70,7 @@ "umbracoBaseUrl": "@ViewBag.UmbracoBaseFolder" }; - + diff --git a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml index 549954bfc1..01afc9f2ec 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml @@ -72,7 +72,7 @@ @*And finally we can load in our angular app*@ - + diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 4924985689..38b178fcfa 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -54,7 +54,7 @@
- + From 00afea61f0b99f3ab6ccdfde98396111d65723d2 Mon Sep 17 00:00:00 2001 From: Thomas Morris Date: Sun, 14 Oct 2018 10:49:50 +0100 Subject: [PATCH 095/585] Update v8 getting started Update link so that it doesn't 404 and replace with relevant link. --- .github/V8_GETTING_STARTED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md index 8cd792aa71..def923e0d0 100644 --- a/.github/V8_GETTING_STARTED.md +++ b/.github/V8_GETTING_STARTED.md @@ -33,5 +33,5 @@ We recommend running the site with the Visual Studio since you'll be able to rem We are keeping track of [known issues and limitations here](http://issues.umbraco.org/issue/U4-11279). These line items will eventually be turned into actual tasks to be worked on. Feel free to help us keep this list updated if you find issues and even help fix some of these items. If there is a particular item you'd like to help fix please mention this on the task and we'll create a sub task for the item to continue discussion there. -There's [a list of tasks for v8 that haven't been completed](http://issues.umbraco.org/issues/U4?q=Due+in+version%3A+8.0.0+%23Unresolved+). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done. +There's [a list of tasks for v8 that haven't been completed](https://issues.umbraco.org/issues?q=&project=U4&tagValue=&release=8.0.0&issueType=&resolvedState=open&search=search). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done. From fb277d80e788b9edd59413b2b9f70c46f7f441d2 Mon Sep 17 00:00:00 2001 From: Jamie Townsend Date: Sun, 14 Oct 2018 12:34:16 +0100 Subject: [PATCH 096/585] Fix for old password not showing/hiding correctly on admin users page #3069 (#3273) --- .../src/views/users/user.controller.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 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 1569b5979b..e32d331d0a 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 @@ -93,8 +93,14 @@ //in the ASP.NET Identity world, this config option will allow an admin user to change another user's password //if the user has access to the user section. So if this editor is being access, the user of course has access to this section. //the authorization check is also done on the server side when submitted. - vm.changePasswordModel.config.allowManuallyChangingPassword = !vm.user.isCurrentUser; - + + // only update the setting if not the current logged in user, otherwise leave the value as it is + // currently set in the web.config + if (!vm.user.isCurrentUser) + { + vm.changePasswordModel.config.allowManuallyChangingPassword = true; + } + vm.loading = false; }); }); From 7a474c6fa44adbb4498069a7895f3d38595d0f5f Mon Sep 17 00:00:00 2001 From: Kim Holzmann Date: Wed, 10 Oct 2018 23:06:18 +0200 Subject: [PATCH 097/585] #3219 Validation error on templates when clicking "save" Error with use of savebutton fixed, use the suppressNotification value to check state. --- .../src/views/templates/edit.controller.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index b23d11dea1..7017054904 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -91,15 +91,15 @@ }, function (err) { + if (suppressNotification) { + vm.page.saveButtonState = "error"; - vm.page.saveButtonState = "error"; - - localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function(data){ - var header = data[0]; - var message = data[1]; - notificationsService.error(header, message); - }); - + localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.error(header, message); + }); + } }); }; From a3de30934d5b26cc60fe602301716f0cd82d7518 Mon Sep 17 00:00:00 2001 From: Lotte Pitcher Date: Sun, 14 Oct 2018 15:44:13 +0100 Subject: [PATCH 098/585] Re-adding the language keys for login greeting on a Sunday (greeting0) that were accidentally removed --- src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/es.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/nb.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml | 1 + 18 files changed, 18 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index df94e02566..f94cb9b1c8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -537,6 +537,7 @@ Obnovte nyní pro uložení práce + Šťastnou super neděli Šťastné šílené pondělí Šťastné husté úterý Šťastnou překrásnou středu diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 2f31f2d24e..e5dfe70db7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -720,6 +720,7 @@ Forny for at gemme dine ændringer + Så er det søndag! Smil, det er mandag! Hurra, det er tirsdag! Hvilken herlig onsdag! diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index 6e26dfeec7..e41a9d2a45 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -535,6 +535,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Erneuern Sie, um Ihre Arbeit zu speichern ... + Einen wunderbaren Sonntag Schönen Montag Einen großartigen Dienstag Wunderbaren Mittwoch diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index cea12955be..3073f8e3e2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -801,6 +801,7 @@ To manage your website, simply open the Umbraco back office and start adding con Renew now to save your work + Happy super Sunday Happy manic Monday Happy tubular Tuesday Happy wonderful Wednesday 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 b2b01c513e..f67748c85f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -816,6 +816,7 @@ To manage your website, simply open the Umbraco back office and start adding con Renew now to save your work + Happy super Sunday Happy manic Monday Happy tubular Tuesday Happy wonderful Wednesday diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index 78fb3b7b2d..3f6808150d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -696,6 +696,7 @@ Renovar su sesión para guardar sus cambios + Feliz super domingo Feliz lunes Tremendo martes Maravilloso miércoles diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 0605474494..8d00284788 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -769,6 +769,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Renouvellez votre session maintenant pour sauvegarder votre travail + Joyeux dimanche détonnant Joyeux lundi lumineux Joyeux mardi magique Joyeux mercredi merveilleux diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index 4b9bda319e..e53abc2b53 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -615,6 +615,7 @@ Runwayをインストールして作られた新しいウェブサイトがど 作業を保存して今すぐ更新 + ハッピー スーパー日曜日 ハッピー マニアック月曜日 ハッピー最高の火曜日 ハッピー ワンダフル水曜日 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 1f3d59bc79..0a20fa07d3 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -511,6 +511,7 @@ Forny innlogging for å lagre + Da er det søndag! Smil, det er mandag! Hurra, det er tirsdag! For en herlig onsdag! diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index 62d6e5419a..65f8ff389e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -609,6 +609,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Vernieuw je sessie om je wijzigingen te behouden + Goede zondag Fijne maandag Fijne dinsdag Fijne woensdag diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index 254a01c179..81cbae19c9 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -736,6 +736,7 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Wznów sesję teraz, aby zapisać swoją pracę + Szczęśliwej super niedzieli Szczęśliwego maniakalnego poniedziałku Szczęśliwego świetnego wtorku Szczęśliwej niesamowitej środy diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 97fe7c61a8..0d40c1f3f0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -912,6 +912,7 @@ © 2001 - %0%
umbraco.com

]]>
+ Сегодня же выходной! Понедельник — день тяжелый... Вот уже вторник... Берегите окружающую среду diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index f16129b315..7a3380996c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -502,6 +502,7 @@ © 2001 - %0%
umbraco.com

]]>
+ Happy super Sunday Happy manic Monday Happy tremendous Tuesday Happy wonderful Wednesday diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index 26c6079f7c..15a2302b5d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -529,6 +529,7 @@ To manage your website, simply open the Umbraco back office and start adding con İşinizi kaydetmek için şimdi Yenile + Pazar Pazartesi Salı Çarşamba diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index c44672a0f9..1a0580ef07 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -638,6 +638,7 @@ 已更新,继续工作。 + 星期一快乐 星期二快乐 星期三快乐 星期四快乐 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml index e9c3fa6460..2b39999363 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml @@ -625,6 +625,7 @@ 已更新,繼續工作。 + 超級星期天快樂 瘋狂星期一快樂 熱鬧星期二快樂 美妙星期三快樂 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml index 1f3d59bc79..0a20fa07d3 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml @@ -511,6 +511,7 @@ Forny innlogging for å lagre + Da er det søndag! Smil, det er mandag! Hurra, det er tirsdag! For en herlig onsdag! diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml index e9c3fa6460..2b39999363 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -625,6 +625,7 @@ 已更新,繼續工作。 + 超級星期天快樂 瘋狂星期一快樂 熱鬧星期二快樂 美妙星期三快樂 From 1f072a383e57cb070c6e960fdf8fb4bd42784b8d Mon Sep 17 00:00:00 2001 From: Carole Rennie Logan Date: Sun, 14 Oct 2018 15:56:22 +0100 Subject: [PATCH 099/585] ContentApp Show/Hide per user Role Making content app work show hide in manifest work for user roles. for example: "show": [ "+role/admin" ] --- .../Manifest/ManifestContentAppDefinition.cs | 19 +++++++++++++++++-- .../ContentEditing/IContentAppDefinition.cs | 7 +++++-- src/Umbraco.Web.UI.Client/package-lock.json | 19 +++++++------------ .../ContentAppDefinitionCollection.cs | 7 +++++-- .../ContentEditorContentAppDefinition.cs | 4 +++- .../ContentInfoContentAppDefinition.cs | 4 +++- .../ListViewContentAppDefinition.cs | 3 ++- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 6b8534a88f..8d1acb843a 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -6,6 +6,9 @@ using System.Text.RegularExpressions; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; + namespace Umbraco.Core.Manifest { @@ -82,9 +85,9 @@ namespace Umbraco.Core.Manifest public string[] Show { get; set; } = Array.Empty(); /// - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { - string partA, partB; + string partA, partB; switch (o) { @@ -112,6 +115,18 @@ namespace Umbraco.Core.Manifest // else iterate over each entry foreach (var rule in rules) { + if (rule.PartA == "role") + { + foreach (var group in userGroups) + { + if (rule.Matches(rule.PartA, group.Alias)) + { + ok = rule.Show; + break; + } + } + } + // if the entry does not apply, skip it if (!rule.Matches(partA, partB)) continue; diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs index 5e0c421742..2d30fc6ba9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Core.Models.ContentEditing +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Models.ContentEditing { /// /// Represents a content app definition. @@ -15,6 +18,6 @@ /// the content app should be displayed or not, and return either a /// instance, or null. /// - ContentApp GetContentAppFor(object source); + ContentApp GetContentAppFor(object source, IEnumerable userGroups); } } diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 35cfa96d67..14d4539d59 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -5464,8 +5464,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -5880,8 +5879,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5937,7 +5935,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5981,14 +5978,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -8526,10 +8521,10 @@ "resolved": "https://registry.npmjs.org/jquery-migrate/-/jquery-migrate-1.4.0.tgz", "integrity": "sha1-4AKOSDHMFH2PIvOCBRbr+5dReaU=" }, - "jquery-ui": { + "jquery-ui-dist": { "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", - "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=" + "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.12.1.tgz", + "integrity": "sha1-XAgV08xvkP9fqvWyaKbiO0ypBPo=" }, "jquery-validation": { "version": "1.17.0", diff --git a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs index 7dda00e62c..bdd7455386 100644 --- a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs +++ b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs @@ -5,6 +5,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { @@ -18,9 +19,11 @@ namespace Umbraco.Web.ContentApps _logger = logger; } - public IEnumerable GetContentAppsFor(object o) + public IEnumerable GetContentAppsFor(object o, IEnumerable userGroups=null) { - var apps = this.Select(x => x.GetContentAppFor(o)).WhereNotNull().OrderBy(x => x.Weight).ToList(); + var currentUser = UmbracoContext.Current.Security.CurrentUser; + var roles = currentUser.Groups; + var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); var aliases = new HashSet(); List dups = null; diff --git a/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs index c2d6341e87..d54d1a44d4 100644 --- a/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { @@ -12,7 +14,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { switch (o) { diff --git a/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs index be7a40f007..de490439ba 100644 --- a/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { @@ -12,7 +14,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { switch (o) { diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs index 5c73b2fa8c..ce3ea258d1 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -22,7 +23,7 @@ namespace Umbraco.Web.ContentApps _propertyEditors = propertyEditors; } - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { string contentTypeAlias, entityType; From f394af20d01d3359ee5d7ab45696edbb81a27880 Mon Sep 17 00:00:00 2001 From: Tim Payne Date: Sun, 14 Oct 2018 16:43:52 +0100 Subject: [PATCH 100/585] Update umbsections.directive.js Update the number of sections to the actual number of sections available at the present time, add a fix to the calculateWidth method so that if less items are allowed than available but there is technically enough room to show them the overflow tray still shows up. On prior version, try adding translation to your allowed user group sections and reload, you can't see the translation section in the top menu, but the overflow menu button won't show until you shrink the screen. --- .../components/application/umbsections.directive.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index 7f906ddcc0..5006087ca5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -12,7 +12,7 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionItemsWidth = []; var evts = []; - var maxSections = 7; + var maxSections = 8; //setup scope vars scope.maxSections = maxSections; @@ -46,8 +46,8 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionsWidth = 0; scope.totalSections = scope.sections.length; scope.maxSections = maxSections; - scope.overflowingSections = 0; - scope.needTray = false; + scope.overflowingSections = scope.maxSections - scope.totalSections; + scope.needTray = scope.sections.length > scope.maxSections; // detect how many sections we can show on the screen for (var i = 0; i < sectionItemsWidth.length; i++) { From 3a6f2545f64698300743b3826d8da9cbfce9c671 Mon Sep 17 00:00:00 2001 From: Carole Rennie Logan Date: Sun, 14 Oct 2018 17:38:38 +0100 Subject: [PATCH 101/585] Update src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs making it case insensitive --- src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 8d1acb843a..accb696351 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -115,7 +115,7 @@ namespace Umbraco.Core.Manifest // else iterate over each entry foreach (var rule in rules) { - if (rule.PartA == "role") + if (rule.PartA.InvariantEquals("role")) { foreach (var group in userGroups) { From 64d7caee783361938641023b13c0f8dacf6e488e Mon Sep 17 00:00:00 2001 From: Carole Rennie Logan Date: Sun, 14 Oct 2018 19:24:18 +0100 Subject: [PATCH 102/585] Update src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs Rework logic on content app permissoins. Roles should override all other. --- .../Manifest/ManifestContentAppDefinition.cs | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index accb696351..7b1d311930 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -87,7 +87,7 @@ namespace Umbraco.Core.Manifest /// public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { - string partA, partB; + string partA, partB; switch (o) { @@ -112,35 +112,49 @@ namespace Umbraco.Core.Manifest { var ok = false; - // else iterate over each entry - foreach (var rule in rules) + //if any role specific rules, deal with them first. + if (rules.Where(x => x.PartA.InvariantEquals("role")).Any()) { - if (rule.PartA.InvariantEquals("role")) + foreach (var rule in rules) { - foreach (var group in userGroups) + if (rule.PartA.InvariantEquals("role")) { - if (rule.Matches(rule.PartA, group.Alias)) + foreach (var group in userGroups) { - ok = rule.Show; - break; + if (rule.Matches(rule.PartA, group.Alias)) + { + ok = rule.Show; + break; + } } } } - - // if the entry does not apply, skip it - if (!rule.Matches(partA, partB)) - continue; - - // if the entry applies, - // if it's an exclude entry, exit, do not display the content app - if (!rule.Show) + // if a role entry, don't let anything else override it. + if (!ok) + { return null; - - // else break - ok to display - ok = true; - break; + } } + else + { + // else iterate over each entry to check for show/hide rules. + foreach (var rule in rules) + { + // if the entry does not apply, skip it + if (!rule.Matches(partA, partB)) + continue; + + // if the entry applies, + // if it's an exclude entry, exit, do not display the content app + if (!rule.Show) + return null; + + // else break - ok to display + ok = true; + break; + } + } // when 'show' is specified, default is to *not* show the content app if (!ok) return null; From 575d7a3ae715c9b8756695cf76818b4f8a942207 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 15 Oct 2018 14:44:26 +0200 Subject: [PATCH 103/585] Cleanup --- src/Umbraco.Core/Components/RelateOnCopyComponent.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs index 5356fa6e30..4ebd309e9f 100644 --- a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs @@ -1,12 +1,10 @@ using Umbraco.Core.Composing; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Components { - //TODO: This should just exist in the content service/repo! [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public sealed class RelateOnCopyComponent : UmbracoComponentBase, IUmbracoCoreComponent From 7757c7060e07a9075f08e8d56237159823820930 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 15 Oct 2018 15:48:26 +0200 Subject: [PATCH 104/585] Refactor ManifestContentAppDefinition for roles --- .../Manifest/ManifestContentAppDefinition.cs | 88 ++++++++++--------- .../Manifest/ManifestContentAppTests.cs | 77 ++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 3 files changed, 126 insertions(+), 40 deletions(-) create mode 100644 src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 7b1d311930..3d4b24d359 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -7,8 +7,6 @@ using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; - namespace Umbraco.Core.Manifest { @@ -106,43 +104,28 @@ namespace Umbraco.Core.Manifest } var rules = _showRules ?? (_showRules = ShowRule.Parse(Show).ToArray()); + var userGroupsList = userGroups.ToList(); - // if no 'show' is specified, then always display the content app - if (rules.Length > 0) + var okRole = false; + var hasRole = false; + var okType = false; + var hasType = false; + + foreach (var rule in rules) { - var ok = false; - - //if any role specific rules, deal with them first. - if (rules.Where(x => x.PartA.InvariantEquals("role")).Any()) + if (rule.PartA.InvariantEquals("role")) { - foreach (var rule in rules) - { - if (rule.PartA.InvariantEquals("role")) - { - foreach (var group in userGroups) - { - if (rule.Matches(rule.PartA, group.Alias)) - { - ok = rule.Show; - break; - } - } - } - } - // if a role entry, don't let anything else override it. - if (!ok) - { - return null; - } - } - else - { - // else iterate over each entry to check for show/hide rules. - foreach (var rule in rules) - { + // if roles have been ok-ed already, skip the rule + if (okRole) + continue; - // if the entry does not apply, skip it - if (!rule.Matches(partA, partB)) + // remember we have role rules + hasRole = true; + + foreach (var group in userGroupsList) + { + // if the entry does not apply, skip + if (!rule.Matches("role", group.Alias)) continue; // if the entry applies, @@ -150,16 +133,41 @@ namespace Umbraco.Core.Manifest if (!rule.Show) return null; - // else break - ok to display - ok = true; + // else ok to display, remember roles are ok, break from userGroupsList + okRole = rule.Show; break; } } - // when 'show' is specified, default is to *not* show the content app - if (!ok) - return null; + else // it is a type rule + { + // if type has been ok-ed already, skip the rule + if (okType) + continue; + + // remember we have type rules + hasType = true; + + // if the entry does not apply, skip it + if (!rule.Matches(partA, partB)) + continue; + + // if the entry applies, + // if it's an exclude entry, exit, do not display the content app + if (!rule.Show) + return null; + + // else ok to display, remember type rules are ok + okType = true; + } } + // if roles rules are specified but not ok, + // or if type roles are specified but not ok, + // cannot display the content app + if ((hasRole && !okRole) || (hasType && !okType)) + return null; + + // else // content app can be displayed return _app ?? (_app = new ContentApp { diff --git a/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs new file mode 100644 index 0000000000..eed0919149 --- /dev/null +++ b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core.Manifest; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Tests.Manifest +{ + [TestFixture] + public class ManifestContentAppTests + { + [Test] + public void Test() + { + var contentType = Mock.Of(); + Mock.Get(contentType).Setup(x => x.Alias).Returns("type1"); + var content = Mock.Of(); + Mock.Get(content).Setup(x => x.ContentType).Returns(contentType); + + var group1 = Mock.Of(); + Mock.Get(group1).Setup(x => x.Alias).Returns("group1"); + var group2 = Mock.Of(); + Mock.Get(group2).Setup(x => x.Alias).Returns("group2"); + + // no rule = ok + AssertDefinition(content, true, Array.Empty(), new [] { group1, group2 }); + + // wildcards = ok + AssertDefinition(content, true, new [] { "+content/*" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+media/*" }, new [] { group1, group2 }); + + // explicitly enabling / disabling + AssertDefinition(content, true, new[] { "+content/type1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "-content/type1" }, new [] { group1, group2 }); + + // when there are type rules, failing to approve the type = no app + AssertDefinition(content, false, new[] { "+content/type2" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+media/type1" }, new [] { group1, group2 }); + + // can have multiple rule, first one that matches = end + AssertDefinition(content, false, new[] { "-content/type1", "+content/*" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "-content/type2", "+content/*" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "+content/*", "-content/type1" }, new [] { group1, group2 }); + + // when there are role rules, failing to approve a role = no app + AssertDefinition(content, false, new[] { "+role/group33" }, new [] { group1, group2 }); + + // wildcards = ok + AssertDefinition(content, true, new[] { "+role/*" }, new [] { group1, group2 }); + + // explicitly enabling / disabling + AssertDefinition(content, true, new[] { "+role/group1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "-role/group1" }, new [] { group1, group2 }); + + // can have multiple rule, first one that matches = end + AssertDefinition(content, true, new[] { "+role/group1", "-role/group2" }, new [] { group1, group2 }); + + // mixed type and role rules, both are evaluated and need to match + AssertDefinition(content, true, new[] { "+role/group1", "+content/type1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+role/group1", "+content/type2" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+role/group33", "+content/type1" }, new [] { group1, group2 }); + } + + private void AssertDefinition(object source, bool expected, string[] show, IReadOnlyUserGroup[] groups) + { + var definition = JsonConvert.DeserializeObject("{" + (show.Length == 0 ? "" : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}"); + var app = definition.GetContentAppFor(source, groups); + if (expected) + Assert.IsNotNull(app); + else + Assert.IsNull(app); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 61ae537529..04bccc8bcb 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -119,6 +119,7 @@ + From 466d782db42a0939219eb21ecb732d35f842c924 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 13:18:21 +1100 Subject: [PATCH 105/585] Fixes issue with umbsetdirtyonchange.directive --- .../directives/validation/umbsetdirtyonchange.directive.js | 7 ++++--- .../src/views/propertyeditors/grid/grid.controller.js | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js index c13680a037..d4e77eda05 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js @@ -7,7 +7,7 @@ var formCtrl = ctrls[0]; - if (ctrls.length > 1) { + if (ctrls.length > 1 && ctrls[1]) { //if an ngModel is supplied, assign a render function which is called when the model is changed var modelCtrl = ctrls[1]; var oldRender = modelCtrl.$render; @@ -17,8 +17,9 @@ if (oldRender) { oldRender(); } - }; - } else { + } + } + else { var initValue = attr.umbSetDirtyOnChange; attr.$observe("umbSetDirtyOnChange", function (newValue) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index b5b9910465..38e32b7451 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -888,8 +888,8 @@ angular.module("umbraco") angular.forEach($scope.availableEditors, function (value, key) { //If no translation is provided, keep using the editor name from the manifest if (localizationService.dictionary.hasOwnProperty("grid_" + value.alias)) { - localizationService.localize("grid_" + value.alias).then(function(value){ - value.name = value; + localizationService.localize("grid_" + value.alias).then(function(v){ + value.name = v; }); } }); From 286f7b32793a6076b89b62967e96eb757e4132ff Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 16:41:06 +1100 Subject: [PATCH 106/585] Gets tree syncing working, updates the GetApplicationTrees to still return a single Root since we require this everywhere for JS, fixes full screen section and makes users a full screen section (and reduces unecessary code), removes code to avoid breaking changes from v7 --- src/Umbraco.Web.UI.Client/package-lock.json | 265 +++++++++--------- .../components/tree/umbtree.directive.js | 32 ++- .../src/common/services/tree.service.js | 23 +- .../src/controllers/navigation.controller.js | 18 +- .../src/less/application/grid.less | 1 + .../src/views/components/tree/umb-tree.html | 43 ++- src/Umbraco.Web/Editors/SectionController.cs | 33 ++- .../Models/Trees/MenuItemCollection.cs | 2 + .../Models/Trees/SectionRootNode.cs | 135 +++++++-- .../Models/Trees/TreeNodeCollection.cs | 10 + .../Trees/ApplicationTreeController.cs | 50 ++-- src/Umbraco.Web/Trees/UserTreeController.cs | 33 +-- 12 files changed, 388 insertions(+), 257 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 35cfa96d67..46acc94295 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -955,7 +955,7 @@ "amqplib": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.2.tgz", - "integrity": "sha1-0tcxPH/6pNELzx5iUt5FkbbMe2M=", + "integrity": "sha512-l9mCs6LbydtHqRniRwYkKdqxVa6XMz3Vw1fh+2gJaaVgTM6Jk3o8RccAKWKtlhT1US5sWrFh+KKxsVUALURSIA==", "dev": true, "optional": true, "requires": { @@ -1293,7 +1293,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, "arrify": { @@ -1333,7 +1333,7 @@ "ast-types": { "version": "0.11.5", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.5.tgz", - "integrity": "sha1-mJCCXWYMA8KDOfMV6foKNg4x7Cg=", + "integrity": "sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw==", "dev": true, "optional": true }, @@ -1362,7 +1362,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "dev": true }, "asynckit": { @@ -1405,7 +1405,7 @@ }, "axios": { "version": "0.15.3", - "resolved": "http://registry.npmjs.org/axios/-/axios-0.15.3.tgz", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.15.3.tgz", "integrity": "sha1-LJ1jiy4ZGgjqHWzJiOrda6W9wFM=", "dev": true, "optional": true, @@ -1415,7 +1415,7 @@ "dependencies": { "follow-redirects": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", "integrity": "sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=", "dev": true, "optional": true, @@ -1512,7 +1512,7 @@ }, "basic-auth": { "version": "1.0.4", - "resolved": "http://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=", "dev": true }, @@ -1736,7 +1736,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -2139,7 +2139,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -2508,7 +2508,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -2631,7 +2631,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -2666,7 +2666,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -2743,7 +2743,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", "dev": true }, "core-util-is": { @@ -2769,7 +2769,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -2884,7 +2884,7 @@ }, "cssnano": { "version": "3.10.0", - "resolved": "http://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", "dev": true, "requires": { @@ -2979,7 +2979,7 @@ "data-uri-to-buffer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha1-dxY+qcINhkG0cH6PGKvfmnjzSDU=", + "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", "dev": true, "optional": true }, @@ -4006,7 +4006,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -4142,7 +4142,7 @@ "engine.io": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", - "integrity": "sha1-Dn751pDrCzVZfx1K0Comyi26OEU=", + "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", "dev": true, "requires": { "accepts": "~1.3.4", @@ -4173,7 +4173,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -4190,7 +4190,7 @@ "engine.io-client": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.6.tgz", - "integrity": "sha1-W96xMPi5SlCsXL63JYPnpKBj3f0=", + "integrity": "sha512-hnuHsFluXnsKOndS4Hv6SvUrgdYx1pk2NqfaDMW+GWdgfU3+/V25Cj7I8a0x92idSpa5PIhJRKxPvp9mnoLsfg==", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -4209,7 +4209,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -4220,7 +4220,7 @@ "engine.io-parser": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", - "integrity": "sha1-TA9M/3mq7su9z96maoI8YIVAkZY=", + "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==", "dev": true, "requires": { "after": "0.8.2", @@ -4316,7 +4316,7 @@ }, "es6-promise": { "version": "3.3.1", - "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", "dev": true }, @@ -4615,7 +4615,7 @@ "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", "dev": true }, "exec-buffer": { @@ -4879,7 +4879,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -5130,7 +5130,7 @@ "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90=", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "dev": true, "optional": true }, @@ -5182,7 +5182,7 @@ }, "finalhandler": { "version": "0.4.0", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=", "dev": true, "requires": { @@ -5194,7 +5194,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -5330,7 +5330,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -5486,14 +5486,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5514,8 +5512,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -5666,7 +5663,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5674,14 +5670,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5700,7 +5694,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5880,8 +5873,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5987,8 +5979,7 @@ "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -6069,7 +6060,7 @@ "get-uri": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.2.tgz", - "integrity": "sha1-XHlecTJvbKEoby/IJXXNK6sq9Xg=", + "integrity": "sha512-ZD325dMZOgerGqF/rF6vZXyFGTAay62svjQIT+X/oU2PtxYpFxvSkbsdi+oxIrsNxlZVd4y8wUDqkaExWTI/Cw==", "dev": true, "optional": true, "requires": { @@ -6090,7 +6081,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -6107,7 +6098,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, "requires": { @@ -6248,7 +6239,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -6368,7 +6359,7 @@ }, "lodash": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", "dev": true }, @@ -6503,7 +6494,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -6576,7 +6567,7 @@ }, "gulp-connect": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/gulp-connect/-/gulp-connect-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.0.0.tgz", "integrity": "sha1-8v3zBq6RFGg2jCKF8teC8T7dr04=", "dev": true, "requires": { @@ -6855,7 +6846,7 @@ }, "ansi-regex": { "version": "0.2.1", - "resolved": "http://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", "dev": true }, @@ -6867,7 +6858,7 @@ }, "chalk": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", "dev": true, "requires": { @@ -6940,7 +6931,7 @@ }, "lodash": { "version": "2.4.1", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", "integrity": "sha1-W3cjA03aTSYuWkb7LFjXzCL3FCA=", "dev": true }, @@ -7030,13 +7021,13 @@ }, "minimist": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", "dev": true }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -7048,7 +7039,7 @@ }, "strip-ansi": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", "dev": true, "requires": { @@ -7232,7 +7223,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -7421,7 +7412,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "dev": true, "requires": { "isarray": "2.0.1" @@ -7556,7 +7547,7 @@ }, "http-errors": { "version": "1.3.1", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", "dev": true, "requires": { @@ -7573,7 +7564,7 @@ "http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, "requires": { "eventemitter3": "^3.0.0", @@ -7584,7 +7575,7 @@ "http-proxy-agent": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha1-5IIb7vWyFCogJr1zkm/lN2McVAU=", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "dev": true, "requires": { "agent-base": "4", @@ -7594,7 +7585,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -7640,7 +7631,7 @@ "https-proxy-agent": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha1-UVUpcPoE1yPgTFbQQXjD+SWSu8A=", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", "dev": true, "requires": { "agent-base": "^4.1.0", @@ -7666,7 +7657,7 @@ }, "iconv-lite": { "version": "0.4.11", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=", "dev": true }, @@ -8090,7 +8081,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -8234,7 +8225,7 @@ "is-my-ip-valid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha1-ezUbjo7dTTmV1NBmaA5mTZRpaCQ=", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", "dev": true, "optional": true }, @@ -8526,10 +8517,10 @@ "resolved": "https://registry.npmjs.org/jquery-migrate/-/jquery-migrate-1.4.0.tgz", "integrity": "sha1-4AKOSDHMFH2PIvOCBRbr+5dReaU=" }, - "jquery-ui": { + "jquery-ui-dist": { "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", - "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=" + "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.12.1.tgz", + "integrity": "sha1-XAgV08xvkP9fqvWyaKbiO0ypBPo=" }, "jquery-validation": { "version": "1.17.0", @@ -8630,7 +8621,7 @@ }, "jsonfile": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { @@ -8818,7 +8809,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -8831,7 +8822,7 @@ "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -8849,7 +8840,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "range-parser": { @@ -8861,7 +8852,7 @@ "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "dev": true, "requires": { "bytes": "3.0.0", @@ -8873,7 +8864,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "utils-merge": { @@ -9043,7 +9034,7 @@ "dependencies": { "iconv-lite": { "version": "0.4.15", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", "dev": true } @@ -9079,7 +9070,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -9553,7 +9544,7 @@ }, "readable-stream": { "version": "2.0.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "optional": true, @@ -9568,7 +9559,7 @@ }, "request": { "version": "2.75.0", - "resolved": "http://registry.npmjs.org/request/-/request-2.75.0.tgz", + "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz", "integrity": "sha1-0rgmiihtoT6qXQGt9dGMyQ9lfZM=", "dev": true, "optional": true, @@ -9690,7 +9681,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "optional": true, "requires": { @@ -9766,7 +9757,7 @@ }, "marked": { "version": "0.3.2", - "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.2.tgz", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.2.tgz", "integrity": "sha1-AV2xWIZEOPJKZL3WGgQotBhwbQk=", "dev": true }, @@ -9814,7 +9805,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -9946,7 +9937,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -9973,7 +9964,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -9987,7 +9978,7 @@ }, "morgan": { "version": "1.6.1", - "resolved": "http://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=", "dev": true, "requires": { @@ -10000,7 +9991,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -13328,7 +13319,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "optional": true, @@ -13342,7 +13333,7 @@ "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "optional": true, "requires": { @@ -13359,7 +13350,7 @@ "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "dev": true, "optional": true, "requires": { @@ -13374,7 +13365,7 @@ "pac-resolver": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", - "integrity": "sha1-auoweH2wqJFwTet4AKcip2FabyY=", + "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", "dev": true, "optional": true, "requires": { @@ -13585,7 +13576,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -13725,7 +13716,7 @@ }, "postcss-calc": { "version": "5.3.1", - "resolved": "http://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", "dev": true, "requires": { @@ -13757,7 +13748,7 @@ }, "postcss-discard-comments": { "version": "2.0.4", - "resolved": "http://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", "dev": true, "requires": { @@ -13775,7 +13766,7 @@ }, "postcss-discard-empty": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", "dev": true, "requires": { @@ -13784,7 +13775,7 @@ }, "postcss-discard-overridden": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", "dev": true, "requires": { @@ -13793,7 +13784,7 @@ }, "postcss-discard-unused": { "version": "2.2.3", - "resolved": "http://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", "dev": true, "requires": { @@ -13844,7 +13835,7 @@ }, "postcss-merge-idents": { "version": "2.1.7", - "resolved": "http://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", "dev": true, "requires": { @@ -13883,7 +13874,7 @@ }, "postcss-minify-font-values": { "version": "1.0.5", - "resolved": "http://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", "dev": true, "requires": { @@ -13894,7 +13885,7 @@ }, "postcss-minify-gradients": { "version": "1.0.5", - "resolved": "http://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", "dev": true, "requires": { @@ -13904,7 +13895,7 @@ }, "postcss-minify-params": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", "dev": true, "requires": { @@ -13916,7 +13907,7 @@ }, "postcss-minify-selectors": { "version": "2.1.1", - "resolved": "http://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", "dev": true, "requires": { @@ -13928,7 +13919,7 @@ }, "postcss-normalize-charset": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", "dev": true, "requires": { @@ -13937,7 +13928,7 @@ }, "postcss-normalize-url": { "version": "3.0.8", - "resolved": "http://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", "dev": true, "requires": { @@ -13959,7 +13950,7 @@ }, "postcss-reduce-idents": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", "dev": true, "requires": { @@ -13969,7 +13960,7 @@ }, "postcss-reduce-initial": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", "dev": true, "requires": { @@ -13978,7 +13969,7 @@ }, "postcss-reduce-transforms": { "version": "1.0.4", - "resolved": "http://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", "dev": true, "requires": { @@ -14000,7 +13991,7 @@ }, "postcss-svgo": { "version": "2.1.6", - "resolved": "http://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", "dev": true, "requires": { @@ -14012,7 +14003,7 @@ }, "postcss-unique-selectors": { "version": "2.0.2", - "resolved": "http://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", "dev": true, "requires": { @@ -14029,7 +14020,7 @@ }, "postcss-zindex": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", "dev": true, "requires": { @@ -14136,7 +14127,7 @@ "lru-cache": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha1-oRdc80lt/IQ2wVbDNLSVWZK85pw=", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "optional": true, "requires": { @@ -14189,7 +14180,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, "qs": { @@ -14258,7 +14249,7 @@ }, "iconv-lite": { "version": "0.4.13", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", "dev": true } @@ -14349,7 +14340,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -14430,7 +14421,7 @@ "redis": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", - "integrity": "sha1-ICKI4/WMSfYHnZevehDhMDrhSwI=", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", "dev": true, "optional": true, "requires": { @@ -14442,7 +14433,7 @@ "redis-commands": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", - "integrity": "sha1-RJWIlBTx6IYmEYCxRC5ylWAtg6I=", + "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==", "dev": true, "optional": true }, @@ -14455,7 +14446,7 @@ }, "reduce-css-calc": { "version": "1.3.0", - "resolved": "http://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", "dev": true, "requires": { @@ -14667,7 +14658,7 @@ "requestretry": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-1.13.0.tgz", - "integrity": "sha1-IT7BAG7rdQ6LjOVBdig9FajVXZQ=", + "integrity": "sha512-Lmh9qMvnQXADGAQxsXHP4rbgO6pffCfuR8XUBdP9aitJcLQJxhp7YZK4xAVYXnPJ5E52mwrfiKQtKonPL8xsmg==", "dev": true, "optional": true, "requires": { @@ -14887,7 +14878,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "sax": { @@ -14964,7 +14955,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -15034,7 +15025,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -15092,7 +15083,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, "shebang-command": { @@ -15330,7 +15321,7 @@ "socket.io-parser": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", - "integrity": "sha1-7S2l7nnxCVUDbj2kE7/X8eTYbI4=", + "integrity": "sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -15342,7 +15333,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -15634,7 +15625,7 @@ "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", + "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { "date-format": "^1.2.0", @@ -15666,7 +15657,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15682,7 +15673,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15737,7 +15728,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -15983,7 +15974,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -16023,7 +16014,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -16176,7 +16167,7 @@ }, "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -16191,7 +16182,7 @@ }, "iconv-lite": { "version": "0.4.13", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", "dev": true }, @@ -16217,7 +16208,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -16432,7 +16423,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, "unc-path-regex": { @@ -16694,7 +16685,7 @@ "uws": { "version": "9.14.0", "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", - "integrity": "sha1-+sg4a+/DOno3BcvVjcR7Qwyk3ZU=", + "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", "dev": true, "optional": true }, @@ -16937,7 +16928,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -17076,7 +17067,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { "async-limiter": "~1.0.0", @@ -17112,7 +17103,7 @@ }, "yargs": { "version": "3.10.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { 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 251683edc8..3212690bbb 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 @@ -174,6 +174,29 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } + /** This will check the section tree loaded and return all actual root nodes based on a tree type (non group nodes, non section groups) */ + function getTreeRootNodes() { + var roots; + if ($scope.tree.root.containsGroups) { + //all children in this case are group nodes, so we want the children of these children + roots = _.reduce( + //get the array of array of children + _.map($scope.tree.root.children, function (n) { + return n.children + }), function (m, p) { + //combine the arrays to one array + return m.concat(p) + }); + } + else { + roots = [$scope.tree.root].concat($scope.tree.root.children); + } + + return _.filter(roots, function (node) { + return node && node.metaData && node.metaData.treeAlias; + }); + } + //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node function loadActiveTree(treeAlias) { @@ -189,12 +212,9 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use return $scope.activeTree; } - var childrenAndSelf = [$scope.tree.root].concat($scope.tree.root.children); - $scope.activeTree = _.find(childrenAndSelf, function (node) { - if (node && node.metaData && node.metaData.treeAlias) { - return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); - } - return false; + var treeRoots = getTreeRootNodes(); + $scope.activeTree = _.find(treeRoots, function (node) { + return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); }); if (!$scope.activeTree) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index a5bbf2f886..3b605453c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -542,11 +542,17 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS root: data }; - for (var i = 0; i < result.root.length; i++) { - var group = result.root[i]; + //format the root + self._formatNodeDataForUseInUI(result.root, result.root.children, args.section); - //we need to format/modify some of the node data to be used in our app. - self._formatNodeDataForUseInUI(group, group.children, args.section); + //if this is a root that contains group nodes, we need to format those manually too + if (result.root.containsGroups) { + for (var i = 0; i < result.root.children.length; i++) { + var group = result.root.children[i]; + + //we need to format/modify some of the node data to be used in our app. + self._formatNodeDataForUseInUI(group, group.children, args.section); + } } //cache this result if a cache key is specified - generally a cache key should ONLY @@ -700,12 +706,15 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!angular.isFunction(node.parent)) { throw "node.parent is not a function, the path cannot be resolved"; } - //all root nodes have metadata key 'treeAlias' + var reversePath = []; var current = node; while (current != null) { reversePath.push(current.id); - if (current.metaData && current.metaData["treeAlias"]) { + + //all tree root nodes (non group, not section root) have a treeAlias so exit if that is the case + //or exit if we cannot traverse further up + if ((current.metaData && current.metaData["treeAlias"]) || !current.parent) { current = null; } else { @@ -714,7 +723,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } return reversePath.reverse(); }, - + syncTree: function(args) { if (!args) { diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index dcc17612b3..2912755ce7 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -184,19 +184,17 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //Listen for section state changes evts.push(eventsService.on("appState.treeState.changed", function (e, args) { - if (args.value.root.length > 0) - { - for (var i = 0; i < args.value.root.length; i++) { - var group = args.value.root[i]; + if (args.key === "currentRootNode") { - if(group.metaData.containsTrees === false){ - $rootScope.emptySection = true; - } + //if the changed state is the currentRootNode, determine if this is a full screen app + if (args.value.root && args.value.root.containsTrees === false) { + $rootScope.emptySection = true; + } + else { + $rootScope.emptySection = false; } } - else { - $rootScope.emptySection = false; - } + })); //Listen for section state changes diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 7ed2abc898..a7b4bd0011 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -167,6 +167,7 @@ body.umb-drawer-is-visible #mainwrapper{ @media (min-width: 1101px) { #contentwrapper, #umb-notifications-wrapper {left: 360px;} #speechbubble {left: 360px;} + .emptySection #contentwrapper {left:0px;} } //empty section modification diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html index 3713312968..a8251cffe6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html @@ -1,6 +1,30 @@ \ No newline at end of file + diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 7c3eb94059..dc7ddb7201 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Web.Trees; using Section = Umbraco.Web.Models.ContentEditing.Section; +using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Editors { @@ -30,6 +31,7 @@ namespace Umbraco.Web.Editors // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that // since tree's by nature are controllers and require request contextual data - and then we have to // remember to inject properties - nasty indeed + // fixme - this controller could/should be able to be created from the container and/or from webapi's IHttpControllerTypeResolver var appTreeController = new ApplicationTreeController(); Current.Container.InjectProperties(appTreeController); appTreeController.ControllerContext = ControllerContext; @@ -49,20 +51,35 @@ namespace Umbraco.Web.Editors if (hasDashboards == false) { //get the first tree in the section and get it's root node route path - var sectionTrees = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; - - //Root node trees are now in collection - var firstTree = sectionTrees.FirstOrDefault(); - - section.RoutePath = firstTree.IsContainer == false || firstTree.Children.Count == 0 - ? firstTree.RoutePath - : firstTree.Children[0].RoutePath; + var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; + section.RoutePath = GetRoutePathForFirstTree(sectionRoot); } } return sectionModels; } + /// + /// Returns the first non root/group node's route path + /// + /// + /// + private string GetRoutePathForFirstTree(TreeRootNode rootNode) + { + if (!rootNode.IsContainer || !rootNode.ContainsTrees) + return rootNode.RoutePath; + + foreach(var node in rootNode.Children) + { + if (node is TreeRootNode groupRoot) + return GetRoutePathForFirstTree(groupRoot);//recurse to get the first tree in the group + else + return node.RoutePath; + } + + return string.Empty; + } + /// /// Returns all the sections that the user has access to /// diff --git a/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs b/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs index 05796015e9..e1fd2218e0 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs @@ -9,6 +9,8 @@ namespace Umbraco.Web.Models.Trees [DataContract(Name = "menuItems", Namespace = "")] public class MenuItemCollection { + public static MenuItemCollection Empty => new MenuItemCollection(); + private readonly MenuItemList _menuItems = new MenuItemList(); public MenuItemCollection() diff --git a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs index 34f8e2d351..4265cbaa7f 100644 --- a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs +++ b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs @@ -1,55 +1,154 @@ -using System.Linq; +using System.Globalization; +using System.Linq; using System.Runtime.Serialization; -using Umbraco.Core; namespace Umbraco.Web.Models.Trees { /// - /// A special tree node that represents the section root node for any section. + /// A tree node that represents various types of root nodes /// /// + /// + /// A represents: + /// * The root node for a section containing a single tree + /// * The root node for a section containing multiple sub-trees + /// * The root node for a section containing groups of multiple sub-trees + /// * The group node in a section containing groups of multiple sub-trees + /// + /// /// This is required to return the tree data for a given section. Some sections may only contain one tree which means it's section /// root should also display a menu, whereas other sections have multiple trees and the section root shouldn't display a menu. - /// - /// The section root also contains an explicit collection of children. + /// + /// + /// The root node also contains an explicit collection of children. + /// /// [DataContract(Name = "node", Namespace = "")] - public sealed class SectionRootNode : TreeNode + public sealed class TreeRootNode : TreeNode { - public static SectionRootNode CreateMultiTreeSectionRoot(string nodeId, TreeNodeCollection children) - { - var sectionRoot = new SectionRootNode(nodeId, "", "") - { - IsContainer = true, - Children = children - }; + private static readonly string RootId = Core.Constants.System.Root.ToString(CultureInfo.InvariantCulture); + private bool _isGroup; - //some metadata as to whether or not this section contains any trees - sectionRoot.AdditionalData["containsTrees"] = children.Any(); + /// + /// Creates a group node for grouped multiple trees + /// + /// + /// + public static TreeRootNode CreateGroupNode(TreeNodeCollection children) + { + var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) + { + IsGroup = true, + Children = children + }; return sectionRoot; } - public static SectionRootNode CreateSingleTreeSectionRoot(string nodeId, string getChildNodesUrl, string menuUrl, string title, TreeNodeCollection children) + /// + /// Creates a section root node for grouped multiple trees + /// + /// + /// + public static TreeRootNode CreateGroupedMultiTreeRoot(TreeNodeCollection children) { - return new SectionRootNode(nodeId, getChildNodesUrl, menuUrl) + var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) + { + IsContainer = true, + Children = children, + ContainsGroups = true + }; + + return sectionRoot; + } + + /// + /// Creates a section root node for non-grouped multiple trees + /// + /// + /// + public static TreeRootNode CreateMultiTreeRoot(TreeNodeCollection children) + { + var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) + { + IsContainer = true, + Children = children + }; + + return sectionRoot; + } + + /// + /// Creates a section root node for a section with a single tree + /// + /// + /// + /// + /// + /// + /// + public static TreeRootNode CreateSingleTreeRoot(string nodeId, string getChildNodesUrl, string menuUrl, string title, TreeNodeCollection children) + { + return new TreeRootNode(nodeId, getChildNodesUrl, menuUrl) { Children = children, Name = title }; } - private SectionRootNode(string nodeId, string getChildNodesUrl, string menuUrl) + /// + /// Private constructor + /// + /// + /// + /// + private TreeRootNode(string nodeId, string getChildNodesUrl, string menuUrl) : base(nodeId, null, getChildNodesUrl, menuUrl) { //default to false IsContainer = false; } + /// + /// Will be true if this is a multi-tree section root node (i.e. contains other trees) + /// [DataMember(Name = "isContainer")] public bool IsContainer { get; private set; } + /// + /// True if this is a group root node + /// + [DataMember(Name = "isGroup")] + public bool IsGroup + { + get => _isGroup; + private set + { + //if a group is true then it is also a container + _isGroup = value; + IsContainer = true; + } + } + + /// + /// True if this root node contains group root nodes + /// + [DataMember(Name = "containsGroups")] + public bool ContainsGroups { get; private set; } + + /// + /// The node's children collection + /// [DataMember(Name = "children")] public TreeNodeCollection Children { get; private set; } + + /// + /// Returns true if there are any children + /// + /// + /// This is used in the UI to configure a full screen section/app + /// + [DataMember(Name = "containsTrees")] + public bool ContainsTrees => Children.Count > 0; } } diff --git a/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs b/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs index 39403f144a..48e9b46dbe 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs @@ -6,5 +6,15 @@ namespace Umbraco.Web.Models.Trees [CollectionDataContract(Name = "nodes", Namespace = "")] public sealed class TreeNodeCollection : List { + public static TreeNodeCollection Empty => new TreeNodeCollection(); + + public TreeNodeCollection() + { + } + + public TreeNodeCollection(IEnumerable nodes) + : base(nodes) + { + } } } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 7e73f44267..53c8d26bc2 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -44,16 +44,12 @@ namespace Umbraco.Web.Trees /// An optional bool (defaults to true), if set to false it will also load uninitialized trees /// [HttpQueryStringFilter("queryStrings")] - public async Task> GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) + public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) { application = application.CleanForXss(); - var rootNodeGroups = new List(); - if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound); - var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - //find all tree definitions that have the current application alias var appTrees = Services.ApplicationTreeService.GetApplicationTrees(application, onlyInitialized).ToArray(); @@ -71,11 +67,10 @@ namespace Umbraco.Web.Trees queryStrings, application); - //this will be null if it cannot convert to ta single root section + //this will be null if it cannot convert to a single root section if (result != null) { - rootNodeGroups.Add(result); - return rootNodeGroups; + return result; } } @@ -93,17 +88,18 @@ namespace Umbraco.Web.Trees //Don't apply fancy grouping logic futher down, if we only have one group of items var hasGroups = CoreTrees.Value.Count > 0; - if (hasGroups == false) + if (!hasGroups) { - var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); + var multiTree = TreeRootNode.CreateMultiTreeRoot(collection); multiTree.Name = Services.TextService.Localize("sections/" + application); - rootNodeGroups.Add(multiTree); - return rootNodeGroups; + return multiTree; } - + + var rootNodeGroups = new List(); + //Group trees by [CoreTree] attribute with a TreeGroup property - foreach(var treeSectionGroup in CoreTrees.Value) + foreach (var treeSectionGroup in CoreTrees.Value) { var treeGroupName = treeSectionGroup.Key; @@ -137,14 +133,14 @@ namespace Umbraco.Web.Trees if (groupNodeCollection.Count > 0) { - var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); + var groupRoot = TreeRootNode.CreateGroupNode(groupNodeCollection); groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); rootNodeGroups.Add(groupRoot); } } - return rootNodeGroups.OrderBy(x => x.Name); + return TreeRootNode.CreateGroupedMultiTreeRoot(new TreeNodeCollection(rootNodeGroups.OrderBy(x => x.Name))); } /// @@ -188,7 +184,7 @@ namespace Umbraco.Web.Trees /// /// /// - private async Task GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application) + private async Task GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application) { var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); if (configTree == null) throw new ArgumentNullException(nameof(configTree)); @@ -202,26 +198,16 @@ namespace Umbraco.Web.Trees throw new InvalidOperationException("Could not create root node for tree " + configTree.Alias); } - //if the root node has a route path, we cannot create a single root section because by specifying the route path this would - //override the dashboard route and that means there can be no dashboard for that section which is a breaking change. - if (string.IsNullOrWhiteSpace(rootNode.Result.RoutePath) == false - && rootNode.Result.RoutePath != "#" - && rootNode.Result.RoutePath != application) - { - //null indicates this cannot be converted - return null; - } - - var sectionRoot = SectionRootNode.CreateSingleTreeSectionRoot( + var sectionRoot = TreeRootNode.CreateSingleTreeRoot( rootId, rootNode.Result.ChildNodesUrl, rootNode.Result.MenuUrl, rootNode.Result.Name, byControllerAttempt.Result); - //This can't be done currently because the root will default to routing to a dashboard and if we disable dashboards for a section - //that is really considered a breaking change. See above. - //sectionRoot.RoutePath = rootNode.Result.RoutePath; + //assign the route path based on the root node, this means it will route there when the section is navigated to + //and no dashboards will be available for this section + sectionRoot.RoutePath = rootNode.Result.RoutePath; foreach (var d in rootNode.Result.AdditionalData) { @@ -233,7 +219,7 @@ namespace Umbraco.Web.Trees var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url, configTree.ApplicationAlias); if (legacyAttempt.Success) { - var sectionRoot = SectionRootNode.CreateSingleTreeSectionRoot( + var sectionRoot = TreeRootNode.CreateSingleTreeRoot( rootId, "", //TODO: I think we'll need this in this situation! Url.GetUmbracoApiService("GetMenu", rootId) diff --git a/src/Umbraco.Web/Trees/UserTreeController.cs b/src/Umbraco.Web/Trees/UserTreeController.cs index db1bca0234..e6bd53ddf8 100644 --- a/src/Umbraco.Web/Trees/UserTreeController.cs +++ b/src/Umbraco.Web/Trees/UserTreeController.cs @@ -33,39 +33,14 @@ namespace Umbraco.Web.Trees protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { - var nodes = new TreeNodeCollection(); - return nodes; + //full screen app without tree nodes + return TreeNodeCollection.Empty; } protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - //Create User - var createMenuItem = menu.Items.CreateMenuItem(Services.TextService.Localize("actions/create")); - createMenuItem.Icon = "add"; - createMenuItem.NavigateToRoute("users/users/overview?subview=users&create=true"); - menu.Items.Add(createMenuItem); - - //This is the same setting used in the global JS for 'showUserInvite' - if (EmailSender.CanSendRequiredEmail) - { - //Invite User (Action import closest type of action to an invite user) - var inviteMenuItem = menu.Items.CreateMenuItem(Services.TextService.Localize("user/invite")); - inviteMenuItem.Icon = "message-unopened"; - inviteMenuItem.NavigateToRoute("users/users/overview?subview=users&invite=true"); - - menu.Items.Add(inviteMenuItem); - } - - return menu; - } - - //There is no context menu options for editing a specific user - //Also we no longer list each user in the tree & in theory never hit this - return menu; + //doesn't have a menu, this is a full screen app without tree nodes + return MenuItemCollection.Empty; } } } From 831657537f92236e076d23be1db7bcb8e7e9eb2d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 16:56:28 +1100 Subject: [PATCH 107/585] tree webforms cleanup --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 13 - src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs | 21 - .../Umbraco/TreeInit.aspx.designer.cs | 69 -- .../Umbraco/controls/PasswordChanger.ascx.cs | 28 - .../controls/PasswordChanger.ascx.designer.cs | 69 -- .../controls/Tree/CustomTreeService.asmx | 1 - .../Umbraco/controls/Tree/TreeControl.ascx | 57 -- .../Umbraco/controls/passwordChanger.ascx | 133 ---- .../Umbraco/dialogs/create.aspx | 67 -- src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm | 9 - .../Umbraco/dialogs/treePicker.aspx | 22 - src/Umbraco.Web.UI/Umbraco/treeInit.aspx | 47 -- src/Umbraco.Web/Umbraco.Web.csproj | 22 - .../controls/Tree/CustomTreeControl.cs | 123 ---- .../controls/Tree/CustomTreeService.asmx | 1 - .../controls/Tree/CustomTreeService.cs | 154 ----- .../umbraco/controls/Tree/JTreeContextMenu.cs | 59 -- .../controls/Tree/JTreeContextMenuItem.cs | 82 --- .../umbraco/controls/Tree/NodeInfo.cs | 32 - .../umbraco/controls/Tree/TreeControl.ascx.cs | 593 ------------------ .../umbraco/dialogs/create.aspx.cs | 160 ----- .../umbraco/dialogs/empty.htm | 9 - .../umbraco/dialogs/treePicker.aspx.cs | 47 -- 23 files changed, 1818 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx delete mode 100644 src/Umbraco.Web.UI/Umbraco/treeInit.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeControl.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.asmx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/NodeInfo.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/create.aspx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/empty.htm delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 2fc8cfc8bc..de52021220 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -198,13 +198,6 @@ umbracoPage.Master - - treeInit.aspx - ASPXCodeBehind - - - treeInit.aspx - @@ -325,7 +318,6 @@ - @@ -340,7 +332,6 @@ - Designer @@ -352,16 +343,13 @@ - - - Designer @@ -445,7 +433,6 @@ Form - diff --git a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs b/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs deleted file mode 100644 index f82be2b80f..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.UI.Umbraco -{ - [Obsolete("Used the TreeControl control instead. This does however get used by the TreeService when requesting the tree init url.")] - public partial class TreeInit : Pages.UmbracoEnsuredPage - { - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - TreeParams = TreeRequestParams.FromQueryStrings().CreateTreeService(); - DataBind(); - } - - protected TreeService TreeParams { get; private set; } - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs b/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs deleted file mode 100644 index 7009fe3289..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs +++ /dev/null @@ -1,69 +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.Web.UI.Umbraco { - - - public partial class TreeInit { - - /// - /// Head1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlHead Head1; - - /// - /// ClientLoader control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web.UI.JavaScript.UmbracoClientDependencyLoader ClientLoader; - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm form1; - - /// - /// ScriptManager1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.ScriptManager ScriptManager1; - - /// - /// JTree control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.controls.Tree.TreeControl JTree; - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs b/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs deleted file mode 100644 index a15ebbf2c7..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration.Provider; -using System.Linq; -using System.Web; -using System.Web.Security; - -namespace Umbraco.Web.UI.Umbraco.Controls -{ - public partial class PasswordChanger : global::umbraco.controls.passwordChanger - { - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - //always reset the control vals - ResetPasswordCheckBox.Checked = false; - umbPasswordChanger_passwordCurrent.Text = null; - umbPasswordChanger_passwordNew.Text = null; - umbPasswordChanger_passwordNewConfirm.Text = null; - //reset the flag always - IsChangingPasswordField.Value = "false"; - this.DataBind(); - } - - - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs b/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs deleted file mode 100644 index 94193a1450..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs +++ /dev/null @@ -1,69 +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.Web.UI.Umbraco.Controls { - - - public partial class PasswordChanger { - - /// - /// ResetPlaceHolder control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder ResetPlaceHolder; - - /// - /// CurrentPasswordPlaceHolder control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder CurrentPasswordPlaceHolder; - - /// - /// CurrentPasswordValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator CurrentPasswordValidator; - - /// - /// NewPasswordRequiredValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator NewPasswordRequiredValidator; - - /// - /// NewPasswordLengthValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RegularExpressionValidator NewPasswordLengthValidator; - - /// - /// Div1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlGenericControl Div1; - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx b/src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx deleted file mode 100644 index 2ddab055f6..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService language="C#" class="umbraco.controls.Tree.CustomTreeService" %> \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx deleted file mode 100644 index 9488fb0643..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx +++ /dev/null @@ -1,57 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="true" Inherits="umbraco.controls.Tree.TreeControl" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="umbClient" Namespace="Umbraco.Web.UI.Bundles" Assembly="Umbraco.Web" %> - - - - - - - - - - - -
-
-
-
\ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx b/src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx deleted file mode 100644 index 9f94a70c45..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx +++ /dev/null @@ -1,133 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="True" CodeBehind="passwordChanger.ascx.cs" Inherits="Umbraco.Web.UI.Umbraco.Controls.PasswordChanger" %> - - - -<%= Services.TextService.Localize("user/changePassword") %>
- - - -
-

- Password has been reset to
-
- <%# ChangingPasswordModel.GeneratedPassword %> -

-
diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx deleted file mode 100644 index e67b5b0b32..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx +++ /dev/null @@ -1,67 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" - AutoEventWireup="True" Inherits="umbraco.dialogs.create" %> - -<%@ Import Namespace="Umbraco.Web" %> -<%@ Register Src="../controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - " /> - - - - - - -
- " onclick="onNodeSelectionConfirmed();" - disabled="true" style="width: 100px" /> -   - <%= Services.TextService.Localize("or") %>  - <%=Services.TextService.Localize("cancel")%> -
-
- - - - - -
diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm b/src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm deleted file mode 100644 index 4df7696ed6..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm +++ /dev/null @@ -1,9 +0,0 @@ - - - - Umbraco - empty document - - - - - diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx deleted file mode 100644 index 9add7d07f2..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx +++ /dev/null @@ -1,22 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" CodeBehind="treePicker.aspx.cs" - AutoEventWireup="True" Inherits="umbraco.dialogs.treePicker" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="umb2" TagName="Tree" Src="../controls/Tree/TreeControl.ascx" %> - - - - - - - - - - diff --git a/src/Umbraco.Web.UI/Umbraco/treeInit.aspx b/src/Umbraco.Web.UI/Umbraco/treeInit.aspx deleted file mode 100644 index aca6ecc07a..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/treeInit.aspx +++ /dev/null @@ -1,47 +0,0 @@ -<%@ Page Language="c#" CodeBehind="TreeInit.aspx.cs" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.TreeInit" %> -<%@ Register Src="controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web.UI.JavaScript" Assembly="Umbraco.Web" %> - - - - - - - - - - - - - - - - - -
- - -
- -
-
- - diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 65be911ced..37f4868bc1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1034,18 +1034,12 @@ - - ASPXCodeBehind - ASPXCodeBehind ASPXCodeBehind - - ASPXCodeBehind - @@ -1205,9 +1199,6 @@ ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind @@ -1265,17 +1256,6 @@ - - ASPXCodeBehind - - - Component - - - - - Code - @@ -1421,7 +1401,6 @@ - ASPXCodeBehind @@ -1430,7 +1409,6 @@ - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeControl.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeControl.cs deleted file mode 100644 index 12b056fedd..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeControl.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.IO; -using System.Web.UI; -using System.Web.UI.HtmlControls; -using ClientDependency.Core; -using umbraco.controls.Tree; -using Umbraco.Core.IO; -using Umbraco.Core.Services; - -namespace umbraco.controls.Tree -{ - /// - /// A custom tree control that uses a custom web service to return the initial node, this is required - /// due to a bug that exists in Umbraco 4.5.1 tree control/web service. - /// - /// - /// Since we're inheriting from a UserControl and all of the ClientDependency registrations are done inline, we need - /// to re-register the ClientDependencies. - /// - [ClientDependency(11, ClientDependencyType.Javascript, "Tree/jquery.tree.js", "UmbracoClient")] - [ClientDependency(12, ClientDependencyType.Javascript, "Tree/UmbracoContext.js", "UmbracoClient")] - [ClientDependency(12, ClientDependencyType.Javascript, "Tree/jquery.tree.contextmenu.js", "UmbracoClient")] - [ClientDependency(12, ClientDependencyType.Javascript, "Tree/jquery.tree.checkbox.js", "UmbracoClient")] - [ClientDependency(12, ClientDependencyType.Javascript, "Tree/NodeDefinition.js", "UmbracoClient")] - [ClientDependency(13, ClientDependencyType.Javascript, "Tree/UmbracoTree.js", "UmbracoClient")] - public class CustomTreeControl : TreeControl - { - /// - /// Ensure child controls are created on init - /// - /// - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - this.EnsureChildControls(); - } - - /// - /// Create the child controls - /// - protected override void CreateChildControls() - { - base.CreateChildControls(); - - TreeContainer = new HtmlGenericControl(); - TreeContainer.TagName = "div"; - TreeContainer.ID = "TreeContainer"; - - this.Controls.Add(TreeContainer); - } - - /// - /// Adds the internal markup to the TreeContainer control - /// - /// - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - //add the internal markup to the TreeContainer - /*
*/ - TreeContainer.Controls.Add(new LiteralControl(@"
")); - } - - /// - /// Render out the correct markup for the tree - /// - /// - /// Since we're inheriting from a UserControl, we need to render out the markup manually - /// - /// - protected override void Render(System.Web.UI.HtmlTextWriter writer) - { - //You'll notice that we're replacing the 'serviceUrl' parameter with our own custom web service! - - writer.Write(@" -"); - - //render the controls - TreeContainer.RenderControl(writer); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.asmx deleted file mode 100644 index 2ddab055f6..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService language="C#" class="umbraco.controls.Tree.CustomTreeService" %> \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.cs deleted file mode 100644 index 6d91847888..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using Umbraco.Core.Security; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Script.Services; -using System.Web.Services; -using System.Web.UI; -using umbraco; -using umbraco.cms.businesslogic; -using umbraco.cms.presentation.Trees; -using umbraco.controls.Tree; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Web.Security; -using Umbraco.Web.WebServices; - -namespace umbraco.controls.Tree -{ - /// - /// Client side ajax utlities for the tree - /// - [ScriptService] - [WebService] - public class CustomTreeService : UmbracoWebService - { - /// - /// Returns some info about the node such as path and id - /// - /// - /// - [WebMethod] - [ScriptMethod(ResponseFormat = ResponseFormat.Json)] - public NodeInfo GetNodeInfo(int id) - { - Authorize(); - - //var node = new CMSNode(id); - var node = Services.EntityService.Get(id); - return new NodeInfo() - { - Id = node.Id, - Path = node.Path, - PathAsNames = string.Join("->", - GetPathNames(node.Path.Split(',') - .Select(x => int.Parse(x)) - .ToArray())) - }; - } - - /// - /// returns the node names for each id passed in - /// - /// - /// - private string[] GetPathNames(int[] ids) - { - return ids - .Where(x => x != -1) - //.Select(x => new CMSNode(x).Text).ToArray(); - .Select(x => Services.EntityService.Get(x).Name).ToArray(); - } - - /// - /// Returns a key/value object with: json, app, js as the keys - /// - /// - [WebMethod] - [ScriptMethod(ResponseFormat = ResponseFormat.Json)] - public Dictionary GetInitAppTreeData(string app, string treeType, bool showContextMenu, bool isDialog, TreeDialogModes dialogMode, string functionToCall, string nodeKey) - { - Authorize(); - - var treeCtl = new TreeControl() - { - ShowContextMenu = showContextMenu, - IsDialog = isDialog, - DialogMode = dialogMode, - App = app, - TreeType = string.IsNullOrEmpty(treeType) ? "" : treeType, //don't set the tree type unless explicitly set - NodeKey = string.IsNullOrEmpty(nodeKey) ? "" : nodeKey, - //StartNodeID = -1, //TODO: set this based on parameters! - FunctionToCall = string.IsNullOrEmpty(functionToCall) ? "" : functionToCall - }; - - var returnVal = new Dictionary(); - - if (string.IsNullOrEmpty(treeType)) - { - //if there's not tree type specified, then render out the tree as per normal with the normal - //way of doing things - returnVal.Add("json", treeCtl.GetJSONInitNode()); - } - else - { - //since 4.5.1 has a bug in it, it ignores if the treeType is specified and will always only render - //the whole APP not just a specific tree. - //this is a work around for this bug until it is fixed (which should be fixed in 4.5.2 - - //get the tree that we need to render - var tree = TreeDefinitionCollection.Instance.FindTree(treeType).CreateInstance(); - tree.ShowContextMenu = showContextMenu; - tree.IsDialog = isDialog; - tree.DialogMode = dialogMode; - tree.NodeKey = string.IsNullOrEmpty(nodeKey) ? "" : nodeKey; - tree.FunctionToCall = string.IsNullOrEmpty(functionToCall) ? "" : functionToCall; - - //now render it's start node - var xTree = new XmlTree(); - - //we're going to hijack the node name here to make it say content/media - var node = tree.RootNode; - if (node.Text.Equals("[FilteredContentTree]")) node.Text = Services.TextService.Localize("content"); - else if (node.Text.Equals("[FilteredMediaTree]")) node.Text = Services.TextService.Localize("media"); - xTree.Add(node); - - returnVal.Add("json", xTree.ToString()); - } - - returnVal.Add("app", app); - returnVal.Add("js", treeCtl.JSCurrApp); - - return returnVal; - } - - internal void Authorize() - { - if (ValidateCurrentUser() == false) - throw new Exception("Client authorization failed. User is not logged in"); - } - - - /// - /// Validates the currently logged in user and ensures they are not timed out - /// - /// - private bool ValidateCurrentUser() - { - var identity = Context.GetCurrentIdentity( - //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! - // Without this check, anything that is using this legacy API, like ui.Text will - // automatically log the back office user in even if it is a front-end request (if there is - // a back office user logged in. This can cause problems becaues the identity is changing mid - // request. For example: http://issues.umbraco.org/issue/U4-4010 - HttpContext.Current.CurrentHandler is Page); - - if (identity != null) - { - return true; - } - return false; - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs deleted file mode 100644 index 726936f570..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Script.Serialization; -using Umbraco.Core.Logging; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; -using Action = Umbraco.Web._Legacy.Actions.Action; - -namespace umbraco.controls.Tree -{ - internal class JTreeContextMenu - { - public string RenderJSONMenu() - { - - JavaScriptSerializer jSSerializer = new JavaScriptSerializer(); - - jSSerializer.RegisterConverters(new List() - { - new JTreeContextMenuItem() - }); - - List allActions = new List(); - foreach (var a in Current.Actions) - { - // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization - try - { - if (!string.IsNullOrEmpty(a.Alias) && (!string.IsNullOrEmpty(a.JsFunctionName) || !string.IsNullOrEmpty(a.JsSource))) - { - // if the action is using invalid javascript we need to do something about this - if (!Action.ValidateActionJs(a)) - { - // Make new Iaction - PlaceboAction pa = new PlaceboAction(a); - pa.JsFunctionName = "IActionProxy_" + pa.Alias.ToSafeAlias() + "()"; - allActions.Add(pa); - - } - else - { - allActions.Add(a); - } - } - } - catch (Exception ex) - { - Current.Logger.Error(ex, "Error initializing tree action"); - } - - } - - - return jSSerializer.Serialize(allActions); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs deleted file mode 100644 index ec59037775..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Script.Serialization; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.controls.Tree -{ - internal class JTreeContextMenuItem : JavaScriptConverter - { - - /// - /// Not implemented as we never need to Deserialize - /// - /// - /// - /// - /// - public override object Deserialize(IDictionary dictionary, Type type, JavaScriptSerializer serializer) - { - throw new NotImplementedException(); - } - - public override IDictionary Serialize(object obj, JavaScriptSerializer serializer) - { - //{ - // "id": "L", - // "label": "Create", - // "icon": "create.png", - // "visible": function(NODE, TREE_OBJ) { if (NODE.length != 1) return false; return TREE_OBJ.check("creatable", NODE); }, - // "action": function(NODE, TREE_OBJ) { TREE_OBJ.create(false, NODE); }, - //} - - - IAction a = (IAction)obj; - Dictionary data = new Dictionary(); - - data.Add("id", a.Letter); - data.Add("label", Current.Services.TextService.Localize(a.Alias)); - - if (a.Icon.StartsWith(".")) - { - StringBuilder sb = new StringBuilder(); - sb.Append(string.Format(""); - sb.Append(""); - data["label"] = sb.ToString(); - } - else - { - data.Add("icon", a.Icon); - } - - return data; - - } - - /// - /// TODO: Find out why we can't just return IAction as one type (JavaScriptSerializer doesn't seem to pick up on it) - /// - public override IEnumerable SupportedTypes - { - get - { - List types = new List(); - foreach (var a in Current.Actions) - { - types.Add(a.GetType()); - } - return types; - } - - - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/NodeInfo.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/NodeInfo.cs deleted file mode 100644 index 2d51a00e64..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/NodeInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace umbraco.controls.Tree -{ - /// - /// Simple data object to hold information about a node - /// - public class NodeInfo - { - /// - /// Gets or sets the id. - /// - /// The id. - public int Id { get; set; } - - /// - /// Gets or sets the path. - /// - /// The path. - public string Path { get; set; } - - /// - /// Gets or sets the path as names. - /// - /// The path as names. - public string PathAsNames { get; set; } - - /// - /// Gets or sets the type of the node. - /// - /// The type of the node. - public string NodeType { get; set; } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs deleted file mode 100644 index 7ec76bc6eb..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs +++ /dev/null @@ -1,593 +0,0 @@ -using System; -using Umbraco.Core.Security; -using System.Collections.Generic; -using System.Web; -using System.Web.UI; -using System.Web.UI.WebControls; -using Umbraco.Core.Models; -using Umbraco.Web.Trees; -using Umbraco.Web.UI.Controls; -using System.Text; -using umbraco.cms.presentation.Trees; -using System.Drawing; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Web.Security; - -namespace umbraco.controls.Tree -{ - - /// - /// The Umbraco tree control. - /// If this control doesn't exist on an UmbracoEnsuredPage it will not work. - /// - public partial class TreeControl : UmbracoUserControl, ITreeService - { - - /// - /// Set the defaults - /// - public TreeControl() - { - Width = Unit.Empty; - Height = Unit.Empty; - BackColor = Color.Empty; - CssClass = ""; - ManualInitialization = false; - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - EnableViewState = false; - } - - public enum TreeMode - { - Standard, Checkbox, InheritedCheckBox - } - - /// - /// If there is not application or tree specified in a query string then this is the application to load. - /// - private const string DEFAULT_APP = Constants.Applications.Content; - - private List m_ActiveTrees = new List(); - private List m_AllAppTrees = new List(); - private List m_ActiveTreeDefs = null; - private TreeMode m_TreeType = TreeMode.Standard; - private bool m_IsInit = false; - private TreeService m_TreeService = new TreeService(); - private string m_SelectedNodePath; - - #region Public Properties - - #region Style Properties - public string CssClass { get; set; } - public Unit Height { get; set; } - public Unit Width { get; set; } - public Color BackColor { get; set; } - #endregion - - #region TreeService parameters. - public string FunctionToCall - { - get { return m_TreeService.FunctionToCall; } - set - { - m_TreeService.FunctionToCall = value; - } - } - - public string NodeKey - { - get { return m_TreeService.NodeKey; } - set - { - m_TreeService.NodeKey = value; - } - } - - public int StartNodeID - { - get { return m_TreeService.StartNodeID; } - set - { - m_TreeService.StartNodeID = value; - } - } - - public string SelectedNodePath - { - get { return m_SelectedNodePath; } - set - { - m_SelectedNodePath = value; - } - } - - public string TreeType - { - get { return m_TreeService.TreeType; } - set - { - m_TreeService.TreeType = value; - } - } - - public bool ShowContextMenu - { - get { return m_TreeService.ShowContextMenu; } - set - { - m_TreeService.ShowContextMenu = value; - } - } - - public bool IsDialog - { - get { return m_TreeService.IsDialog; } - set - { - m_TreeService.IsDialog = value; - } - } - - public TreeDialogModes DialogMode - { - get { return m_TreeService.DialogMode; } - set - { - m_TreeService.DialogMode = value; - } - } - - - public string App - { - get - { - return GetCurrentApp(); - } - set - { - m_TreeService.App = value; - } - } - #endregion - - /// - /// Allows for checkboxes to be used with the tree. Default is standard. - /// - public TreeMode Mode - { - get - { - return m_TreeType; - } - set - { - m_TreeType = value; - } - } - - /// - /// Returns the required JavaScript as a string for the current application - /// - public string JSCurrApp - { - get - { - StringBuilder javascript = new StringBuilder(); - foreach (BaseTree bTree in m_AllAppTrees) - bTree.RenderJS(ref javascript); - return javascript.ToString(); - } - } - - /// - /// By default this is false. If set to true, then the code in the client side of the tree will force calling rebuildTree - /// to be called explicitly for the tree to render - /// - public bool ManualInitialization { get; set; } - - #endregion - - /// - /// Can be set explicitly which will override what is in query strings or what has been set by properties. - /// Useful for rendering out a tree dynamically with an instance of anoterh TreeService. - /// By using this method, it will undo any of the tree service public properties that may be set - /// on this object. - /// - public void SetTreeService(TreeService srv) - { - m_TreeService = srv; - Initialize(); - } - - /// - /// Initializes the control and looks up the tree structures that are required to be rendered. - /// Properties of the control (or SetTreeService) need to be set before pre render or calling - /// GetJSONContextMenu or GetJSONNode - /// - protected void Initialize() - { - //use the query strings if the TreeParams isn't explicitly set - if (m_TreeService == null) - { - m_TreeService = TreeRequestParams.FromQueryStrings().CreateTreeService(); - } - m_TreeService.App = GetCurrentApp(); - - // Validate permissions - if (ValidateCurrentUser() == false) - return; - - if (!Security.ValidateUserApp(GetCurrentApp())) - throw new ArgumentException("The current user doesn't have access to this application. Please contact the system administrator."); - - //find all tree definitions that have the current application alias that are ACTIVE. - //if an explicit tree has been requested, then only load that tree in. - //m_ActiveTreeDefs = TreeDefinitionCollection.Instance.FindActiveTrees(GetCurrentApp()); - - m_ActiveTreeDefs = Services.ApplicationTreeService.GetApplicationTrees(GetCurrentApp(), true).ToList(); - - if (!string.IsNullOrEmpty(this.TreeType)) - { - m_ActiveTreeDefs = m_ActiveTreeDefs - .Where(x => x.Alias == this.TreeType) - .ToList(); //this will only return 1 - } - - //find all tree defs that exists for the current application regardless of if they are active - var appTreeDefs = Services.ApplicationTreeService.GetApplicationTrees(GetCurrentApp()).ToList(); - - //Create the BaseTree's based on the tree definitions found - foreach (var treeDef in appTreeDefs) - { - //create the tree and initialize it - var bTree = LegacyTreeDataConverter.GetLegacyTreeForLegacyServices(treeDef); - //BaseTree bTree = treeDef.CreateInstance(); - bTree.SetTreeParameters(m_TreeService); - - //store the created tree - m_AllAppTrees.Add(bTree); - if (treeDef.Initialize) - m_ActiveTrees.Add(bTree); - } - - m_IsInit = true; - } - - /// - /// Validates the currently logged in user and ensures they are not timed out - /// - /// - private bool ValidateCurrentUser() - { - var identity = Context.GetCurrentIdentity( - //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! - // Without this check, anything that is using this legacy API, like ui.Text will - // automatically log the back office user in even if it is a front-end request (if there is - // a back office user logged in. This can cause problems becaues the identity is changing mid - // request. For example: http://issues.umbraco.org/issue/U4-4010 - HttpContext.Current.CurrentHandler is Page); - - if (identity != null) - { - return true; - } - return false; - } - - - /// - /// This calls the databind method to bind the data binding syntax on the front-end. - /// - /// Databinding was used instead of inline tags in case the tree properties needed to be set - /// by other classes at runtime - /// - /// - /// - /// - /// This will initialize the control so all TreeService properties need to be set before hand - /// - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - if (!m_IsInit) - Initialize(); - - //Render out the JavaScript associated with all of the trees for the application - RenderTreeJS(); - - //apply the styles - if (Width != Unit.Empty) - TreeContainer.Style.Add(HtmlTextWriterStyle.Width, Width.ToString()); - if (Height != Unit.Empty) - TreeContainer.Style.Add(HtmlTextWriterStyle.Height, Height.ToString()); - if (BackColor != Color.Empty) - TreeContainer.Style.Add(HtmlTextWriterStyle.BackgroundColor, ColorTranslator.ToHtml(BackColor)); - if (CssClass != "") - { - TreeContainer.Attributes.Add("class", CssClass); - } - else - { - //add the default class - TreeContainer.Attributes.Add("class", "treeContainer"); - } - - - DataBind(); - } - - /// - /// Returns the JSON markup for the full context menu - /// - public string GetJSONContextMenu() - { - if (ShowContextMenu) - { - JTreeContextMenu menu = new JTreeContextMenu(); - return menu.RenderJSONMenu(); - } - else - { - return "{}"; - } - - } - - /// - /// Returns a string with javascript proxy methods for IActions that are using old javascript - /// - /// - public string GetLegacyIActionJavascript() - { - return LegacyTreeJavascript.GetLegacyIActionJavascript(); - } - - /// - /// Returns the JSON markup for one node - /// - /// - /// - /// - /// - /// This will initialize the control so all TreeService properties need to be set before hand - /// - public string GetJSONNode(string nodeId) - { - if (!m_IsInit) - Initialize(); - - if (string.IsNullOrEmpty(m_TreeService.TreeType)) - { - throw new ArgumentException("The TreeType is not set on the tree service"); - } - - BaseTree tree = m_ActiveTrees.Find( - delegate(BaseTree t) - { - return (t.TreeAlias == m_TreeService.TreeType); - } - ); - return tree.GetSerializedNodeData(nodeId); - } - - /// - /// Returns the JSON markup for the first node in the tree - /// - - public string GetJSONInitNode() - { - if (!m_IsInit) - Initialize(); - - //if there is only one tree to render, we don't want to have a node to hold sub trees, we just want the - //stand alone tree, so we'll just add a TreeType to the TreeService and ensure that the right method gets loaded in tree.aspx - if (m_ActiveTrees.Count == 1) - { - m_TreeService.TreeType = m_ActiveTreeDefs[0].Alias; - - //convert the menu to a string - //string initActions = (TreeSvc.ShowContextMenu ? Action.ToString(m_ActiveTrees[0].RootNodeActions) : ""); - - //Since there's only 1 tree, render out the tree's RootNode properties - XmlTree xTree = new XmlTree(); - xTree.Add(m_ActiveTrees[0].RootNode); - return xTree.ToString(); - } - else - { - - //If there is more than 1 tree for the application than render out a - //container node labelled with the current application. - XmlTree xTree = new XmlTree(); - XmlTreeNode xNode = XmlTreeNode.CreateRoot(new NullTree(GetCurrentApp())); - xNode.Text = Services.TextService.Localize("sections", GetCurrentApp()); - xNode.Source = m_TreeService.GetServiceUrl(); - xNode.Action = "javascript:" + global::Umbraco.Web.UI.Pages.ClientTools.Scripts.OpenDashboard(GetCurrentApp()); - xNode.NodeType = m_TreeService.App.ToLower(); - xNode.NodeID = "-1"; - xNode.Icon = ".sprTreeFolder"; - xTree.Add(xNode); - return xTree.ToString(); - } - } - - private void RenderTreeJS() - { - Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "Trees_" + GetCurrentApp(), JSCurrApp, true); - } - - /// - /// Return the current application alias. If neither the TreeType of Application is specified - /// than return the default application. If the Application is null but there is a TreeType then - /// find the application that the tree type is associated with. - /// - private string GetCurrentApp() - { - //if theres an treetype specified but no application - if (string.IsNullOrEmpty(m_TreeService.App) && - !string.IsNullOrEmpty(m_TreeService.TreeType)) - { - TreeDefinition treeDef = TreeDefinitionCollection.Instance.FindTree(m_TreeService.TreeType); - if (treeDef != null) - return treeDef.App.Alias; - } - else if (!string.IsNullOrEmpty(m_TreeService.App)) - return m_TreeService.App; - - //if everything is null then return the default app - return DEFAULT_APP; - } - - /// - /// CssInclude2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude2; - - /// - /// CssInclude3 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude3; - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// JsInclude2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude2; - - /// - /// JsInclude3 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude3; - - /// - /// JsInclude4 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude4; - - /// - /// JsInclude5 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude5; - - /// - /// JsInclude6 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude6; - - /// - /// JsInclude8 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude8; - - /// - /// JsInclude11 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude11; - - /// - /// JsInclude7 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude7; - - /// - /// JsInclude12 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude12; - - /// - /// JsInclude9 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude9; - - /// - /// JsInclude10 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude10; - - /// - /// TreeContainer control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlGenericControl TreeContainer; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/create.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/create.aspx.cs deleted file mode 100644 index f35f9d93b6..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/create.aspx.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System; -using System.Linq; -using System.Globalization; -using System.Web.UI; -using System.Xml; -using Umbraco.Core.IO; -using Umbraco.Web; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Web.UI.Pages; -using Umbraco.Web._Legacy.Actions; -using Button = System.Web.UI.WebControls.Button; -using UserControl = System.Web.UI.UserControl; - -namespace umbraco.dialogs -{ - /// - /// Summary description for create. - /// - public partial class create : UmbracoEnsuredPage - { - protected Button ok; - - private string _app; - protected string App - { - get - { - if (_app == null) - { - _app = Request.CleanForXss("app"); - //validate the app - if (Services.SectionService.GetSections().Any(x => x.Alias.InvariantEquals(_app)) == false) - { - throw new InvalidOperationException("A requested app: " + Request.GetItemAsString("app") + " was not found"); - } - } - return _app; - } - } - - protected void Page_Load(object sender, EventArgs e) - { - // Put user code to initialize the page here - if (Request.GetItemAsString("nodeId") == "") - { - var appType = Services.TextService.Localize("sections", App).ToLower(); - pane_chooseNode.Text = Services.TextService.Localize("create/chooseNode", new[] { appType }) + "?"; - - DataBind(); - } - else - { - int nodeId = Request.GetItemAs("nodeId"); - //ensure they have access to create under this node!! - if (App.InvariantEquals(Constants.Applications.Media) || CheckCreatePermissions(nodeId)) - { - //var c = new CMSNode(nodeId); - var c = Services.EntityService.Get(nodeId); - path.Value = c.Path; - pane_chooseNode.Visible = false; - panel_buttons.Visible = false; - pane_chooseName.Visible = true; - var createDef = new XmlDocument(); - var defReader = new XmlTextReader(Server.MapPath(IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/config/create/UI.xml")); - createDef.Load(defReader); - defReader.Close(); - - // Find definition for current nodeType - XmlNode def = createDef.SelectSingleNode("//nodeType [@alias = '" + App + "']"); - phCreate.Controls.Add(new UserControl().LoadControl(IOHelper.ResolveUrl(SystemDirectories.Umbraco) + def.SelectSingleNode("./usercontrol").FirstChild.Value)); - } - else - { - PageNameHolder.type = Umbraco.Web._Legacy.Controls.Feedback.feedbacktype.error; - PageNameHolder.Text = Services.TextService.Localize("rights") + " " + Services.TextService.Localize("error"); - JTree.DataBind(); - } - } - } - - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference(IOHelper.ResolveUrl(SystemDirectories.WebServices) + "/legacyAjaxCalls.asmx")); - } - - private bool CheckCreatePermissions(int nodeId) - { - var c = Services.EntityService.Get(nodeId); - var permission = Services.UserService.GetPermissions(Security.CurrentUser, c.Path); - return permission.AssignedPermissions.Contains(ActionNew.Instance.Letter.ToString(CultureInfo.InvariantCulture), StringComparer.Ordinal); - } - - - /// - /// path control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlInputHidden path; - - /// - /// pane_chooseNode control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_chooseNode; - - /// - /// JTree control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.controls.Tree.TreeControl JTree; - - /// - /// panel_buttons control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel panel_buttons; - - /// - /// PageNameHolder control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Feedback PageNameHolder; - - /// - /// pane_chooseName control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_chooseName; - - /// - /// phCreate control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder phCreate; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/empty.htm b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/empty.htm deleted file mode 100644 index 4df7696ed6..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/empty.htm +++ /dev/null @@ -1,9 +0,0 @@ - - - - Umbraco - empty document - - - - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs deleted file mode 100644 index 23cc004e61..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using umbraco.cms.presentation.Trees; - -namespace umbraco.dialogs -{ - [Obsolete("Use the TreeControl instead. This does however get used by the TreeService when requesting the tree init url.")] - public partial class treePicker : Umbraco.Web.UI.Pages.UmbracoEnsuredPage - { - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - TreeParams = TreeRequestParams.FromQueryStrings().CreateTreeService(); - DataBind(); - - if(Request.QueryString["selected"] != null && TreeParams.TreeType == "content") - { - var currContent = Services.ContentService.GetById(int.Parse(Request.QueryString["selected"])); - if (currContent != null) - { - if (currContent.ParentId > 0) - DialogTree.SelectedNodePath = currContent.Path; - } - } - } - - protected TreeService TreeParams { get; private set; } - - /// - /// DialogTree control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.controls.Tree.TreeControl DialogTree; - - } -} From 10873c1dfc37271c2f0a5b82f7da5abf4b3b55a3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 17:41:02 +1100 Subject: [PATCH 108/585] Cleans up all old tree classes. woohoo! --- .../Composing/TypeLoaderTests.cs | 7 - .../common/mocks/umbraco.servervariables.js | 4 +- .../src/common/services/assets.service.js | 4 - src/Umbraco.Web.UI/Umbraco/Create.aspx.cs | 6 - .../developer/Macros/EditMacro.aspx.cs | 4 - .../Editors/BackOfficeController.cs | 34 +- .../Editors/BackOfficeServerVariables.cs | 1 - .../Editors/PackageInstallController.cs | 5 - src/Umbraco.Web/Models/Trees/MenuItem.cs | 27 +- .../Trees/ApplicationTreeController.cs | 23 - .../Trees/ApplicationTreeExtensions.cs | 121 ---- src/Umbraco.Web/Trees/LegacyTreeController.cs | 109 ---- .../Trees/LegacyTreeDataConverter.cs | 245 -------- src/Umbraco.Web/Trees/LegacyTreeJavascript.cs | 88 --- src/Umbraco.Web/Trees/LegacyTreeParams.cs | 37 -- src/Umbraco.Web/Trees/UsersTreeController.cs | 96 --- src/Umbraco.Web/TypeLoaderExtensions.cs | 13 +- src/Umbraco.Web/Umbraco.Web.csproj | 23 +- .../umbraco/Trees/BaseTree.cs | 571 ------------------ .../umbraco/Trees/ITreeService.cs | 20 - .../umbraco/Trees/NodeActionsEventArgs.cs | 18 - .../umbraco/Trees/NullTree.cs | 62 -- .../umbraco/Trees/TreeDefinition.cs | 110 ---- .../umbraco/Trees/TreeDefinitionCollection.cs | 187 ------ .../umbraco/Trees/TreeDialogModes.cs | 17 - .../umbraco/Trees/TreeEventArgs.cs | 26 - .../umbraco/Trees/TreeRequestParams.cs | 135 ----- .../umbraco/Trees/TreeService.cs | 132 ---- .../umbraco/Trees/TreeUrlGenerator.cs | 189 ------ .../umbraco/Trees/XmlTree.cs | 455 -------------- .../developer/Packages/installer.aspx.cs | 103 ---- 31 files changed, 5 insertions(+), 2867 deletions(-) delete mode 100644 src/Umbraco.Web/Trees/LegacyTreeController.cs delete mode 100644 src/Umbraco.Web/Trees/LegacyTreeJavascript.cs delete mode 100644 src/Umbraco.Web/Trees/LegacyTreeParams.cs delete mode 100644 src/Umbraco.Web/Trees/UsersTreeController.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/ITreeService.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NodeActionsEventArgs.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NullTree.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinition.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDialogModes.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeEventArgs.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeRequestParams.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeService.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeUrlGenerator.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index d7f2e7dd53..9b23ec3d6b 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -275,13 +275,6 @@ AnotherContentFinder Assert.AreEqual(34, actions.Count()); } - [Test] - public void Resolves_Trees() - { - var trees = _typeLoader.GetTrees(); - Assert.AreEqual(1, trees.Count()); - } - [Test] public void GetDataEditors() { diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js index 7ba14485d4..da6f78a6a5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js @@ -13,8 +13,6 @@ Umbraco.Sys.ServerVariables = { "mediaTypeApiBaseUrl": "/umbraco/Api/MediaType/", "macroApiBaseUrl": "/umbraco/Api/Macro/", "authenticationApiBaseUrl": "/umbraco/UmbracoApi/Authentication/", - //For this we'll just provide a file that exists during the mock session since we don't really have legay js tree stuff - "legacyTreeJs": "/belle/lib/lazyload/empty.js", "serverVarsJs": "/belle/lib/lazyload/empty.js", "imagesApiBaseUrl": "/umbraco/UmbracoApi/Images/", "entityApiBaseUrl": "/umbraco/UmbracoApi/Entity/", @@ -39,4 +37,4 @@ Umbraco.Sys.ServerVariables = { assemblyVersion: "1", version: "7" } -}; \ No newline at end of file +}; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index a331af899b..e7f40f4814 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -141,10 +141,6 @@ angular.module('umbraco.services') var self = this; return self.loadJs(umbRequestHelper.getApiUrl("serverVarsJs", "", ""), $rootScope).then(function () { initAssetsLoaded = true; - - //now we need to go get the legacyTreeJs - but this can be done async without waiting. - self.loadJs(umbRequestHelper.getApiUrl("legacyTreeJs", "", ""), $rootScope); - return loadMomentLocaleForCurrentUser(); }); } diff --git a/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs b/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs index 220b86fa5f..7848a5976b 100644 --- a/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs +++ b/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs @@ -1,11 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Web; -using System.Xml; -using Umbraco.Core; -using Umbraco.Core.IO; -using umbraco.cms.presentation.Trees; using Umbraco.Web._Legacy.UI; namespace Umbraco.Web.UI.Umbraco diff --git a/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs b/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs index b9c0577c95..96433be9cc 100644 --- a/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs +++ b/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Web.UI; using System.Web.UI.WebControls; @@ -9,12 +8,9 @@ using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -using umbraco.cms.presentation.Trees; using System.Linq; -using Umbraco.Web.UI; using Umbraco.Web.UI.Pages; using Umbraco.Core.Services; -using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web._Legacy.Controls; diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index f3bda66b51..15554b7f50 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -520,38 +520,6 @@ namespace Umbraco.Web.Editors return true; } - /// - /// Returns the JavaScript blocks for any legacy trees declared - /// - /// - [UmbracoAuthorize(Order = 0)] - [MinifyJavaScriptResult(Order = 1)] - public JavaScriptResult LegacyTreeJs() - { - Func getResult = () => - { - var javascript = new StringBuilder(); - javascript.AppendLine(LegacyTreeJavascript.GetLegacyTreeJavascript()); - javascript.AppendLine(LegacyTreeJavascript.GetLegacyIActionJavascript()); - //add all of the menu blocks - foreach (var file in GetLegacyActionJs(LegacyJsActionType.JsBlock)) - { - javascript.AppendLine(file); - } - return javascript.ToString(); - }; - - //cache the result if debugging is disabled - var result = HttpContext.IsDebuggingEnabled - ? getResult() - : ApplicationCache.RuntimeCache.GetCacheItem( - typeof(BackOfficeController) + "LegacyTreeJs", - () => getResult(), - new TimeSpan(0, 10, 0)); - - return JavaScript(result); - } - internal static IEnumerable GetLegacyActionJsForActions(LegacyJsActionType type, IEnumerable values) { var blockList = new List(); @@ -582,7 +550,7 @@ namespace Umbraco.Web.Editors } /// - /// Renders out all JavaScript references that have bee declared in IActions + /// Renders out all JavaScript references that have been declared in IActions /// private static IEnumerable GetLegacyActionJs(LegacyJsActionType type) { diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 8eb1c4247f..b0cdcb72b9 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -112,7 +112,6 @@ namespace Umbraco.Web.Editors {"externalLoginsUrl", _urlHelper.Action("ExternalLogin", "BackOffice")}, {"externalLinkLoginsUrl", _urlHelper.Action("LinkLogin", "BackOffice")}, - {"legacyTreeJs", _urlHelper.Action("LegacyTreeJs", "BackOffice")}, {"manifestAssetList", _urlHelper.Action("GetManifestAssetList", "BackOffice")}, {"gridConfig", _urlHelper.Action("GetGridConfig", "BackOffice")}, //TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 6111a931e3..94465feab8 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using System.Web.Http; using System.Xml; using umbraco.cms.businesslogic.packager; -using umbraco.cms.presentation.Trees; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Events; @@ -249,7 +248,6 @@ namespace Umbraco.Web.Editors // trigger the UninstalledPackage event PackagingService.OnUninstalledPackage(new UninstallPackageEventArgs(summary, false)); - TreeDefinitionCollection.Instance.ReRegisterTrees(); } /// @@ -587,9 +585,6 @@ namespace Umbraco.Web.Editors var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( UmbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); - //clear the tree cache - we'll do this here even though the browser will reload, but just in case it doesn't can't hurt. - //these bits are super old, but cant find another way to do this currently - global::umbraco.cms.presentation.Trees.TreeDefinitionCollection.Instance.ReRegisterTrees(); var redirectUrl = ""; if (ins.Control.IsNullOrWhiteSpace() == false) diff --git a/src/Umbraco.Web/Models/Trees/MenuItem.cs b/src/Umbraco.Web/Models/Trees/MenuItem.cs index a854bbe777..88d772b939 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItem.cs @@ -201,32 +201,7 @@ namespace Umbraco.Web.Models.Trees } } } - - internal void ConvertLegacyFileSystemMenuItem(string path, string nodeType, string currentSection) - { - // try to get a URL/title from the legacy action, - // in some edge cases, item can be null so we'll just convert those to "-1" and "" for id and name since these edge cases don't need that. - var attempt = LegacyTreeDataConverter.GetUrlAndTitleFromLegacyAction(Action, - path, - nodeType, - path, currentSection); - if (attempt) - { - var action = attempt.Result; - LaunchDialogUrl(action.Url, action.DialogTitle); - } - else - { - // if that doesn't work, try to get the legacy confirm view - var attempt2 = LegacyTreeDataConverter.GetLegacyConfirmView(Action); - if (attempt2) - { - var view = attempt2.Result; - var textService = Current.Services.TextService; - LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + path + "' ?"); - } - } - } + #endregion } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 53c8d26bc2..b4d217fe62 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -167,12 +167,6 @@ namespace Umbraco.Web.Trees return null; } - var legacyAttempt = configTree.TryGetRootNodeFromLegacyTree(queryStrings, Url, configTree.ApplicationAlias); - if (legacyAttempt.Success) - { - return legacyAttempt.Result; - } - throw new ApplicationException("Could not get root node for tree type " + configTree.Alias); } @@ -216,23 +210,6 @@ namespace Umbraco.Web.Trees return sectionRoot; } - var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url, configTree.ApplicationAlias); - if (legacyAttempt.Success) - { - var sectionRoot = TreeRootNode.CreateSingleTreeRoot( - rootId, - "", //TODO: I think we'll need this in this situation! - Url.GetUmbracoApiService("GetMenu", rootId) - + "&parentId=" + rootId - + "&treeType=" + application - + "§ion=" + application, - "", //TODO: I think we'll need this in this situation! - legacyAttempt.Result); - - - sectionRoot.AdditionalData.Add("treeAlias", configTree.Alias); - return sectionRoot; - } throw new ApplicationException("Could not render a tree for type " + configTree.Alias); } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index af9eb094d0..171601a338 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -13,12 +13,10 @@ using Umbraco.Core; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using umbraco.cms.presentation.Trees; using Umbraco.Core.Composing; using Umbraco.Core.Services; using Current = Umbraco.Web.Composing.Current; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; -using UrlHelper = System.Web.Http.Routing.UrlHelper; namespace Umbraco.Web.Trees { @@ -191,125 +189,6 @@ namespace Umbraco.Web.Trees return Attempt.Succeed(instance.GetNodes(id, formCollection)); } - internal static Attempt TryGetRootNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper, string currentSection) - { - var xmlTreeNodeAttempt = TryGetRootXmlNodeFromLegacyTree(appTree, formCollection, urlHelper); - if (xmlTreeNodeAttempt.Success == false) - { - return Attempt.Fail(xmlTreeNodeAttempt.Exception); - } - - //the root can potentially be null, in that case we'll just return a null success which means it won't be included - if (xmlTreeNodeAttempt.Result == null) - { - return Attempt.Succeed(null); - } - - //var temp = new LegacyTreeController(xmlTreeNodeAttempt.Result, appTree.Alias, currentSection, urlHelper); - var temp = new TreeControllerBaseStuffForLegacy(appTree.Alias, xmlTreeNodeAttempt.Result.Text, urlHelper); - var newRoot = temp.GetRootNode(formCollection); - - return Attempt.Succeed(newRoot); - - } - - internal static Attempt TryGetRootXmlNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) - { - var treeDefAttempt = appTree.TryGetLegacyTreeDef(); - if (treeDefAttempt.Success == false) - { - return Attempt.Fail(treeDefAttempt.Exception); - } - var treeDef = treeDefAttempt.Result; - var bTree = treeDef.CreateInstance(); - var treeParams = new LegacyTreeParams(formCollection); - bTree.SetTreeParameters(treeParams); - - var xmlRoot = bTree.RootNode; - - return Attempt.Succeed(xmlRoot); - } - - internal static Attempt TryGetLegacyTreeDef(this ApplicationTree appTree) - { - //This is how the legacy trees worked.... - var treeDef = TreeDefinitionCollection.Instance.FindTree(appTree.Alias); - return treeDef == null - ? Attempt.Fail(new InstanceNotFoundException("Could not find tree of type " + appTree.Alias)) - : Attempt.Succeed(treeDef); - } - - internal static Attempt TryLoadFromLegacyTree(this ApplicationTree appTree, string id, FormDataCollection formCollection, UrlHelper urlHelper, string currentSection) - { - var xTreeAttempt = appTree.TryGetXmlTree(id, formCollection); - if (xTreeAttempt.Success == false) - { - return Attempt.Fail(xTreeAttempt.Exception); - } - return Attempt.Succeed(LegacyTreeDataConverter.ConvertFromLegacy(id, xTreeAttempt.Result, urlHelper, currentSection, formCollection)); - } - - internal static Attempt TryGetMenuFromLegacyTreeRootNode(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) - { - var rootAttempt = appTree.TryGetRootXmlNodeFromLegacyTree(formCollection, urlHelper); - if (rootAttempt.Success == false) - { - return Attempt.Fail(rootAttempt.Exception); - } - - var currentSection = formCollection.GetRequiredString("section"); - - var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(rootAttempt.Result, currentSection); - return Attempt.Succeed(result); - } - - internal static Attempt TryGetMenuFromLegacyTreeNode(this ApplicationTree appTree, string parentId, string nodeId, FormDataCollection formCollection, UrlHelper urlHelper) - { - var xTreeAttempt = appTree.TryGetXmlTree(parentId, formCollection); - if (xTreeAttempt.Success == false) - { - return Attempt.Fail(xTreeAttempt.Exception); - } - - var currentSection = formCollection.GetRequiredString("section"); - - var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(nodeId, xTreeAttempt.Result, currentSection); - if (result == null) - { - return Attempt.Fail(new ApplicationException("Could not find the node with id " + nodeId + " in the collection of nodes contained with parent id " + parentId)); - } - return Attempt.Succeed(result); - } - - private static Attempt TryGetXmlTree(this ApplicationTree appTree, string id, FormDataCollection formCollection) - { - var treeDefAttempt = appTree.TryGetLegacyTreeDef(); - if (treeDefAttempt.Success == false) - { - return Attempt.Fail(treeDefAttempt.Exception); - } - var treeDef = treeDefAttempt.Result; - //This is how the legacy trees worked.... - var bTree = treeDef.CreateInstance(); - var treeParams = new LegacyTreeParams(formCollection); - - //we currently only support an integer id or a string id, we'll refactor how this works - //later but we'll get this working first - int startId; - if (int.TryParse(id, out startId)) - { - treeParams.StartNodeID = startId; - } - else - { - treeParams.NodeKey = id; - } - var xTree = new XmlTree(); - bTree.SetTreeParameters(treeParams); - bTree.Render(ref xTree); - return Attempt.Succeed(xTree); - } - } } diff --git a/src/Umbraco.Web/Trees/LegacyTreeController.cs b/src/Umbraco.Web/Trees/LegacyTreeController.cs deleted file mode 100644 index 87035e0632..0000000000 --- a/src/Umbraco.Web/Trees/LegacyTreeController.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.Trees -{ - /// - /// This is used to output JSON from legacy trees - /// - [PluginController("UmbracoTrees"), LegacyTreeAuthorizeAttribute] - public class LegacyTreeController : TreeControllerBase - { - private readonly XmlTreeNode _xmlTreeNode; - private readonly string _currentSection; - - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) - { - return LegacyTreeDataConverter.ConvertFromLegacy( - _xmlTreeNode.NodeID, - _xmlTreeNode, - Url, - _currentSection, - queryStrings, - isRoot: true); - } - - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var tree = GetTree(queryStrings); - var attempt = tree.TryLoadFromLegacyTree(id, queryStrings, Url, tree.ApplicationAlias); - if (attempt.Success == false) - { - Logger.Error(attempt.Exception, "Could not render tree {TreeType} for node id {NodeId}", queryStrings.GetRequiredString("treeType"), id); - throw new ApplicationException("Could not render tree " + queryStrings.GetRequiredString("treeType") + " for node id " + id); - } - - return attempt.Result; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - //get the parent id from the query strings - var parentId = queryStrings.GetRequiredString("parentId"); - var tree = GetTree(queryStrings); - - var rootIds = new[] - { - Core.Constants.System.Root.ToString(CultureInfo.InvariantCulture), - Core.Constants.System.RecycleBinContent.ToString(CultureInfo.InvariantCulture), - Core.Constants.System.RecycleBinMedia.ToString(CultureInfo.InvariantCulture) - }; - - //if the id and the parentId are both -1 then we need to get the menu for the root node - if (rootIds.Contains(id) && parentId == "-1") - { - var attempt = tree.TryGetMenuFromLegacyTreeRootNode(queryStrings, Url); - if (attempt.Success == false) - { - Logger.Error(attempt.Exception, "Could not render menu for root node for treeType {TreeType}", queryStrings.GetRequiredString("treeType")); - throw new ApplicationException("Could not render menu for root node for treeType " + queryStrings.GetRequiredString("treeType")); - } - - foreach (var menuItem in attempt.Result.Items) - { - menuItem.Name = Services.TextService.Localize("actions", menuItem.Alias); - } - return attempt.Result; - } - else - { - var attempt = tree.TryGetMenuFromLegacyTreeNode(parentId, id, queryStrings, Url); - if (attempt.Success == false) - { - Logger.Error(attempt.Exception, "Could not render menu for treeType {TreeType} for node id {ParentNodeId}", queryStrings.GetRequiredString("treeType"), parentId); - throw new ApplicationException("Could not render menu for treeType " + queryStrings.GetRequiredString("treeType") + " for node id " + parentId); - } - foreach (var menuItem in attempt.Result.Items) - { - menuItem.Name = Services.TextService.Localize("actions", menuItem.Alias); - } - return attempt.Result; - } - } - - public override string RootNodeDisplayName { get; } - - public override string TreeAlias { get; } - - private ApplicationTree GetTree(FormDataCollection queryStrings) - { - //need to ensure we have a tree type - var treeType = queryStrings.GetRequiredString("treeType"); - //now we'll look up that tree - var tree = Services.ApplicationTreeService.GetByAlias(treeType); - if (tree == null) - throw new InvalidOperationException("No tree found with alias " + treeType); - return tree; - } - - } -} diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index a0259ab247..131c7954c8 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Routing; -using umbraco.cms.presentation.Trees; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Web._Legacy.Actions; using Umbraco.Web.Composing; @@ -19,154 +17,6 @@ namespace Umbraco.Web.Trees /// internal class LegacyTreeDataConverter { - internal static BaseTree GetLegacyTreeForLegacyServices(Core.Models.ApplicationTree appTree) - { - if (appTree == null) throw new ArgumentNullException("appTree"); - - BaseTree tree; - - var controllerAttempt = appTree.TryGetControllerTree(); - if (controllerAttempt.Success) - { - Current.Logger.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services."); - return null; - - //var legacyAtt = controllerAttempt.Result.GetCustomAttribute(false); - //if (legacyAtt == null) - //{ - // Current.Logger.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services unless attributed with " + typeof(LegacyBaseTreeAttribute)); - // return null; - //} - - //var treeDef = new TreeDefinition( - // legacyAtt.BaseTreeType, - // new ApplicationTree(true, appTree.SortOrder, appTree.ApplicationAlias, appTree.Alias, appTree.Title, appTree.IconClosed, appTree.IconOpened, legacyAtt.BaseTreeType.GetFullNameWithAssembly()), - // new Section(appTree.Alias, appTree.Alias, "", 0)); - - //tree = treeDef.CreateInstance(); - //tree.TreeAlias = appTree.Alias; - - } - else - { - //get the tree that we need to render - var treeDef = TreeDefinitionCollection.Instance.FindTree(appTree.Alias); - if (treeDef == null) - { - return null; - } - tree = treeDef.CreateInstance(); - } - - return tree; - } - - /// - /// This is used by any legacy services that require rendering a BaseTree, if a new controller tree is detected it will try to invoke it's legacy predecessor. - /// - /// - /// - /// - internal static BaseTree GetLegacyTreeForLegacyServices(IApplicationTreeService appTreeService, string treeType) - { - if (appTreeService == null) throw new ArgumentNullException("appTreeService"); - if (treeType == null) throw new ArgumentNullException("treeType"); - - //first get the app tree definition so we can then figure out if we need to load by legacy or new - //now we'll look up that tree - var appTree = appTreeService.GetByAlias(treeType); - if (appTree == null) - throw new InvalidOperationException("No tree found with alias " + treeType); - - return GetLegacyTreeForLegacyServices(appTree); - } - - /// - /// Gets the menu item collection from a legacy tree node based on it's parent node's child collection - /// - /// The node id - /// The node collection that contains the node id - /// - /// - internal static MenuItemCollection ConvertFromLegacyMenu(string nodeId, XmlTree xmlTree, string currentSection) - { - var xmlTreeNode = xmlTree.treeCollection.FirstOrDefault(x => x.NodeID == nodeId); - if (xmlTreeNode == null) - { - return null; - } - - return ConvertFromLegacyMenu(xmlTreeNode, currentSection); - } - - /// - /// Gets the menu item collection from a legacy tree node - /// - /// - /// - /// - internal static MenuItemCollection ConvertFromLegacyMenu(XmlTreeNode xmlTreeNode, string currentSection) - { - var collection = new MenuItemCollection(); - - var menuItems = xmlTreeNode.Menu.ToArray(); - var numAdded = 0; - var seperators = new List(); - foreach (var t in menuItems) - { - if (t is ContextMenuSeperator && numAdded > 0) - { - //store the index for which the seperator should be placed - seperators.Add(collection.Items.Count()); - } - else - { - var menuItem = collection.Items.Add(t, Current.Services.TextService.Localize("actions", t.Alias)); - - var currentAction = t; - - // try to get a URL/title from the legacy action - var attempt = GetUrlAndTitleFromLegacyAction(currentAction, xmlTreeNode.NodeID, xmlTreeNode.NodeType, xmlTreeNode.Text, currentSection); - if (attempt) - { - var action = attempt.Result; - menuItem.LaunchDialogUrl(action.Url, action.DialogTitle); - } - else - { - // if that doesn't work, try to get the legacy confirm view - var attempt2 = GetLegacyConfirmView(currentAction); - if (attempt2) - { - var view = attempt2.Result; - var textService = Current.Services.TextService; - menuItem.LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + xmlTreeNode.Text + "' ?"); - } - else - { - // if that doesn't work and there's no jsAction in there already then add the legacy js method call - if (menuItem.AdditionalData.ContainsKey(MenuItem.JsActionKey) == false) - menuItem.ExecuteLegacyJs(menuItem.Action.JsFunctionName); - } - } - - numAdded++; - } - } - var length = collection.Items.Count(); - foreach (var s in seperators) - { - if (length >= s) - { - collection.Items.ElementAt(s).SeperatorBefore = true; - } - } - - return collection; - } - - - /// /// This will look at the legacy IAction's JsFunctionName and convert it to a confirmation dialog view if possible /// @@ -261,101 +111,6 @@ namespace Umbraco.Web.Trees return Attempt.Fail(); } - /// - /// Converts a legacy XmlTreeNode to a new TreeNode - /// - /// - /// - /// - /// - /// - /// The current query strings for the request - this is used to append the query strings to the menu URL of the item being rendered since the menu - /// actually belongs to this same node (request) the query strings need to exist so the menu can be rendered in some cases. - /// - /// - /// - internal static TreeNode ConvertFromLegacy(string parentId, XmlTreeNode xmlTreeNode, UrlHelper urlHelper, string currentSection, FormDataCollection currentQueryStrings, bool isRoot = false) - { - // /umbraco/tree.aspx?rnd=d0d0ff11a1c347dabfaa0fc75effcc2a&id=1046&treeType=content&contextMenu=false&isDialog=false - - //we need to convert the node source to our legacy tree controller - var childNodesSource = urlHelper.GetUmbracoApiService("GetNodes"); - - var childQuery = (xmlTreeNode.Source.IsNullOrWhiteSpace() || xmlTreeNode.Source.IndexOf('?') == -1) - ? "" - : xmlTreeNode.Source.Substring(xmlTreeNode.Source.IndexOf('?')); - - //append the query strings - childNodesSource = childNodesSource.AppendQueryStringToUrl(childQuery); - - //for the menu source we need to detect if this is a root node since we'll need to set the parentId and id to -1 - // for which we'll handle correctly on the server side. - //if there are no menu items, then this will be empty - var menuSource = ""; - if (xmlTreeNode.Menu != null && xmlTreeNode.Menu.Any()) - { - menuSource = urlHelper.GetUmbracoApiService("GetMenu"); - //these are the absolute required query strings - var menuQueryStrings = new Dictionary - { - {"id", (isRoot ? "-1" : xmlTreeNode.NodeID)}, - {"treeType", xmlTreeNode.TreeType}, - {"parentId", (isRoot ? "-1" : parentId)}, - {"section", currentSection} - }; - //append the extra ones on this request - foreach (var i in currentQueryStrings.Where(x => menuQueryStrings.Keys.Contains(x.Key) == false)) - { - menuQueryStrings.Add(i.Key, i.Value); - } - - menuSource = menuSource.AppendQueryStringToUrl(menuQueryStrings.ToQueryString()); - } - - - //TODO: Might need to add stuff to additional attributes - - var node = new TreeNode(xmlTreeNode.NodeID, isRoot ? null : parentId, childNodesSource, menuSource) - { - HasChildren = xmlTreeNode.HasChildren, - Icon = xmlTreeNode.Icon, - Name = xmlTreeNode.Text, - NodeType = xmlTreeNode.NodeType - }; - if (isRoot) - { - node.AdditionalData.Add("treeAlias", xmlTreeNode.TreeType); - } - - foreach (var appliedClass in xmlTreeNode.Style.AppliedClasses) - { - node.CssClasses.Add(appliedClass); - } - - //This is a special case scenario, we know that content/media works based on the normal Belle routing/editing so we'll ensure we don't - // pass in the legacy JS handler so we do it the new way, for all other trees (Currently, this is a WIP), we'll render - // the legacy js callback,. - var knownNonLegacyNodeTypes = new[] { "content", "contentRecycleBin", "mediaRecyleBin", "media" }; - if (knownNonLegacyNodeTypes.InvariantContains(xmlTreeNode.NodeType) == false) - { - node.AssignLegacyJsCallback(xmlTreeNode.Action); - } - return node; - } - - internal static TreeNodeCollection ConvertFromLegacy(string parentId, XmlTree xmlTree, UrlHelper urlHelper, string currentSection, FormDataCollection currentQueryStrings) - { - //TODO: Once we get the editor URL stuff working we'll need to figure out how to convert - // that over to use the old school ui.xml stuff for these old trees and however the old menu items worked. - - var collection = new TreeNodeCollection(); - foreach (var x in xmlTree.treeCollection) - { - collection.Add(ConvertFromLegacy(parentId, x, urlHelper, currentSection, currentQueryStrings)); - } - return collection; - } - internal class LegacyUrlAction { public LegacyUrlAction(string url, string dialogTitle) diff --git a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs b/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs deleted file mode 100644 index 0ae9916d4b..0000000000 --- a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Logging; -using umbraco.cms.presentation.Trees; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; - -namespace Umbraco.Web.Trees -{ - /// - /// A class used to render the legacy JS requirements for trees and IActions. - /// - internal static class LegacyTreeJavascript - { - /// - /// If any legacy tree requires any JS rendering then we will compile a JS output of the combination. - /// - /// - public static string GetLegacyTreeJavascript() - { - //find all tree defs that exists for the current application regardless of if they are active - List appTreeDefs = TreeDefinitionCollection.Instance; - //Create the BaseTree's based on the tree definitions found - var legacyTrees = appTreeDefs.Select(treeDef => treeDef.CreateInstance()).ToList(); - var javascript = new StringBuilder(); - foreach (var bTree in legacyTrees) - { - try - { - bTree.RenderJS(ref javascript); - } - catch (Exception ex) - { - Current.Logger.Error(typeof(LegacyTreeJavascript), ex, "Could not load the JS from the legacy tree {TreeAlias}", bTree.TreeAlias); - } - } - - return ReplaceLegacyJs(javascript.ToString()); - } - - /// - /// Returns a string with javascript proxy methods for IActions that are using old javascript - /// - /// - public static string GetLegacyIActionJavascript() - { - var js = new StringBuilder(); - foreach (var a in Current.Actions) - { - // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization - try - { - if (string.IsNullOrEmpty(a.Alias) == false && (string.IsNullOrEmpty(a.JsFunctionName) == false || string.IsNullOrEmpty(a.JsSource) == false)) - { - // if the action is using invalid javascript we need to do something about this - if (global::Umbraco.Web._Legacy.Actions.Action.ValidateActionJs(a) == false) - { - js.AppendLine("function IActionProxy_" + a.Alias.ToSafeAlias() + "() {"); - js.AppendLine(global::Umbraco.Web._Legacy.Actions.Action.ConvertLegacyJs(a.JsFunctionName)); - js.AppendLine("}"); - } - } - } - catch (Exception ex) - { - Current.Logger.Error(typeof(LegacyTreeJavascript), ex, "Error initializing tree action"); - } - } - - if (js.Length != 0) - { - js.Insert(0, "// This javascript is autogenerated by Umbraco to ensure legacy compatiblity with old context menu items\n\n"); - } - - return ReplaceLegacyJs(js.ToString()); - } - - private static string ReplaceLegacyJs(string js){ - js = js.Replace("parent.right.document.location", "UmbClientMgr.getFakeFrame()"); - js = js.Replace("right.document.location", "UmbClientMgr.getFakeFrame()"); - - return js; - } - } -} diff --git a/src/Umbraco.Web/Trees/LegacyTreeParams.cs b/src/Umbraco.Web/Trees/LegacyTreeParams.cs deleted file mode 100644 index 13b84edd38..0000000000 --- a/src/Umbraco.Web/Trees/LegacyTreeParams.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.Trees -{ - - //Temporary, but necessary until we refactor trees in general - internal class LegacyTreeParams : ITreeService - { - public LegacyTreeParams() - { - - } - - public LegacyTreeParams(IEnumerable> formCollection) - { - if (formCollection != null) - { - var p = TreeRequestParams.FromDictionary(formCollection.ToDictionary(x => x.Key, x => x.Value)); - NodeKey = p.NodeKey; - StartNodeID = p.StartNodeID; - ShowContextMenu = p.ShowContextMenu; - IsDialog = p.IsDialog; - DialogMode = p.DialogMode; - FunctionToCall = p.FunctionToCall; - } - } - - public string NodeKey { get; set; } - public int StartNodeID { get; set; } - public bool ShowContextMenu { get; set; } - public bool IsDialog { get; set; } - public TreeDialogModes DialogMode { get; set; } - public string FunctionToCall { get; set; } - } -} diff --git a/src/Umbraco.Web/Trees/UsersTreeController.cs b/src/Umbraco.Web/Trees/UsersTreeController.cs deleted file mode 100644 index 2fe08e25fb..0000000000 --- a/src/Umbraco.Web/Trees/UsersTreeController.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Web._Legacy.Actions; -using Umbraco.Core.Services; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Trees -{ - [UmbracoTreeAuthorize(Constants.Trees.Users)] - [Tree(Constants.Applications.Users, Constants.Trees.Users, null)] - [PluginController("UmbracoTrees")] - [CoreTree] - public class UsersTreeController : TreeController - { - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var nodes = new TreeNodeCollection(); - - var users = new List(Services.UserService.GetAll(0, int.MaxValue, out _)); - var currentUser = UmbracoContext.Current.Security.CurrentUser; - var hideDisabledUsers = UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice; - - foreach (var user in users.OrderBy(x => x.IsApproved == false)) - { - // hide disabled user - if (user.IsApproved == false && hideDisabledUsers) - continue; - - if (user.IsSuper()) - { - // only super can see super - if (!currentUser.IsSuper()) continue; - } - else if (user.IsAdmin()) - { - // only admins can see admins - if (!currentUser.IsAdmin()) continue; - } - - var node = CreateTreeNode( - user.Id.ToString(CultureInfo.InvariantCulture), - "-1", - queryStrings, - user.Name, - "icon-user", - false, - "/" + queryStrings.GetValue("application") + "/framed/" - + Uri.EscapeDataString("users/EditUser.aspx?id=" + user.Id)); - - if (user.IsApproved == false) - node.CssClasses.Add("not-published"); - - nodes.Add(node); - } - - return nodes; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - // Root actions - menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) - .ConvertLegacyMenuItem(null, "users", queryStrings.GetValue("application")); - - menu.Items.Add( - Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); - return menu; - } - - // If administator, don't create a menu - if (id == "0") - return menu; - - menu.Items.Add(new DisableUser() - { - Name = Services.TextService.Localize("actions", "disable") - }); - - return menu; - } - } -} diff --git a/src/Umbraco.Web/TypeLoaderExtensions.cs b/src/Umbraco.Web/TypeLoaderExtensions.cs index a1209abccf..710c342115 100644 --- a/src/Umbraco.Web/TypeLoaderExtensions.cs +++ b/src/Umbraco.Web/TypeLoaderExtensions.cs @@ -4,7 +4,6 @@ using Umbraco.Core.Media; using Umbraco.Web.Mvc; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using umbraco.cms.presentation.Trees; using Umbraco.Core.Composing; using Umbraco.Web._Legacy.Actions; @@ -43,17 +42,7 @@ namespace Umbraco.Web { return mgr.GetTypes(); } - - /// - /// Returns all available ITrees in application - /// - /// - /// - internal static IEnumerable GetTrees(this TypeLoader mgr) - { - return mgr.GetTypes(); - } - + /// /// Returns all available ISearchableTrees in application /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 37f4868bc1..faf08849ce 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -177,6 +177,7 @@ + @@ -510,17 +511,12 @@ - - - ASPXCodeBehind - ASPXCodeBehind - @@ -637,7 +633,6 @@ - @@ -961,11 +956,7 @@ - - - - @@ -1326,23 +1317,11 @@ rollBack.aspx - - Code - - - - - - - - - - CheckForUpgrade.asmx diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs deleted file mode 100644 index 1592ff68bb..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs +++ /dev/null @@ -1,571 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// All ITree's should inherit from BaseTree. - /// - public abstract class BaseTree : ITreeService //, IApplicationEventHandler - { - protected BaseTree(string application) - { - app = application; - } - - protected const string FolderIcon = "icon-folder"; - protected const string FolderIconOpen = "icon-folder"; - - internal static string GetTreePathFromFilePath(string filePath) - => GetTreePathFromFilePath(filePath, true, false); - - internal static string GetTreePathFromFilePath(string filePath, bool includeInit, bool urlEncode) - { - List treePath = new List(); - treePath.Add("-1"); - if (includeInit) treePath.Add("init"); - string[] pathPaths = filePath.Split('/'); - pathPaths.Reverse(); - for (int p = 0; p < pathPaths.Length; p++) - { - var s = string.Join("/", pathPaths.Take(p + 1).ToArray()); - if (urlEncode) s = WebUtility.UrlEncode(s); - treePath.Add(s); - } - string sPath = string.Join(",", treePath.ToArray()); - return sPath; - } - - /// - /// Returns the node definition of the root node for this tree - /// - public XmlTreeNode RootNode - { - get - { - Initialize(); - return m_initNode; - } - } - - /// - /// By default the init actions that are allowed for all trees are Create, Reload Nodes. - /// These are the menu items that show up in the context menu for the root node of the current tree. - /// Should be used in conjunction with the RootNode property - /// - public List RootNodeActions - { - get - { - Initialize(); - return m_initActions; - } - } - - /// - /// The actions that are allowed to be performed on this tree. These are the items that may show up on the - /// context menu for a given node. - /// - public List AllowedActions - { - get - { - Initialize(); - return m_allowedActions; - } - } - - /// - /// The tree alias name. By default, if a BaseTree is instantiated by it's TreeDefinition, then the TreeAlias will be - /// the name defined in the database. Inheritors can override this property to set the TreeAlias to whatever they choose. - /// - public virtual string TreeAlias - { - get - { - if (string.IsNullOrEmpty(m_treeAlias)) - { - TreeDefinition treeDef = TreeDefinitionCollection.Instance.FindTree(this); - m_treeAlias = (treeDef != null ? treeDef.Tree.Alias : ""); - } - - return m_treeAlias; - } - internal set { m_treeAlias = value; } - } - private string m_treeAlias; - - #region ITreeService Members - - /// - /// By default the start node id will be -1 which will return all of the nodes - /// - public virtual int StartNodeID - { - get { return -1; } - } - - public bool ShowContextMenu - { - get { return m_showContextMenu; } - set { m_showContextMenu = value; } - } - - public bool IsDialog - { - get { return m_isDialog; } - set { m_isDialog = value; } - } - - /// - /// The NodeKey is a string representation of the nodeID. Generally this is used for tree's whos node's unique key value is a string in instead - /// of an integer such as folder names. - /// - public string NodeKey - { - get { return m_nodeKey; } - set { m_nodeKey = value; } - } - - public string FunctionToCall - { - get { return m_functionToCall; } - set { m_functionToCall = value; } - } - - public TreeDialogModes DialogMode - { - get { return m_dialogMode; } - set { m_dialogMode = value; } - } - - #endregion - - #region ITree Members - - /// - /// The ID of the node to render. This is generally set before calling the render method of the tree. If it is not set then the - /// StartNodeID property is used as the node ID to render. - /// - public virtual int id - { - set { m_id = value; } - get { return m_id; } - } - public virtual string app - { - set { m_app = value; } - get { return m_app; } - } - - /// - /// Renders out any JavaScript methods that may be required for tree functionality. Generally used to load the editor page when - /// a user clicks on a tree node. - /// - /// - public abstract void RenderJS(ref StringBuilder Javascript); - - /// - /// Classes need to override thid method to create the nodes for the XmlTree - /// - /// - public abstract void Render(ref XmlTree tree); - - #endregion - - protected int m_id; - protected string m_app; - protected XmlTreeNode m_initNode; - private List m_initActions = new List(); - private List m_allowedActions = new List(); - - //these are the request parameters that can be specified. - //since we want to remove the querystring/httpcontext dependency from - //our trees, we need to define these as properties. - private bool m_showContextMenu = true; - private bool m_isDialog = false; - private TreeDialogModes m_dialogMode = TreeDialogModes.none; - private string m_nodeKey = ""; - private string m_functionToCall = ""; - - private bool m_isInitialized = false; - - private XmlTree m_xTree = new XmlTree(); - - /// - /// Provides easy access to the ServiceContext - /// - protected internal ServiceContext Services - { - get { return Current.Services; } - } - - /// - /// Initializes the class if it hasn't been done already - /// - protected void Initialize() - { - if (!m_isInitialized) - { - //VERY IMPORTANT! otherwise it will go infinite loop! - m_isInitialized = true; - - CreateAllowedActions(); //first create the allowed actions - - //raise the event, allow developers to modify the collection - var nodeActions = new NodeActionsEventArgs(false, m_allowedActions); - OnNodeActionsCreated(nodeActions); - m_allowedActions = nodeActions.AllowedActions; - - CreateRootNodeActions();//then create the root node actions - - var rootActions = new NodeActionsEventArgs(true, m_initActions); - OnNodeActionsCreated(rootActions); - m_initActions = rootActions.AllowedActions; - - CreateRootNode(); //finally, create the root node itself - } - } - - /// - /// This method creates the Root node definition for the tree. - /// Inheritors must override this method to create their own definition. - /// - /// - protected abstract void CreateRootNode(ref XmlTreeNode rootNode); - protected void CreateRootNode() - { - m_initNode = XmlTreeNode.CreateRoot(this); - m_initNode.Icon = FolderIcon; - m_initNode.OpenIcon = FolderIconOpen; - CreateRootNode(ref m_initNode); - } - - - /// - /// This method creates the IAction list for the tree's root node. - /// Inheritors can override this method to create their own Context menu. - /// - /// - protected virtual void CreateRootNodeActions(ref List actions) - { - actions.AddRange(GetDefaultRootNodeActions()); - } - protected void CreateRootNodeActions() - { - CreateRootNodeActions(ref m_initActions); - } - - /// - /// This method creates the AllowedActions IAction list for the tree's nodes. - /// Inheritors can override this method to create their own Context menu. - /// - /// - protected virtual void CreateAllowedActions(ref List actions) - { - actions.Add(ActionDelete.Instance); - - //raise the event, allow developers to modify the collection - var e = new NodeActionsEventArgs(false, actions); - OnNodeActionsCreated(e); - actions = e.AllowedActions; - - } - protected void CreateAllowedActions() - { - CreateAllowedActions(ref m_allowedActions); - } - - /// - /// A helper method to re-generate the root node for the current tree. - /// - /// - public XmlTreeNode GenerateRootNode() - { - XmlTreeNode node = XmlTreeNode.CreateRoot(this); - this.CreateRootNode(ref node); - return node; - } - - /// - /// This method can initialize the ITreeService parameters for this class with another ITreeService object. - /// This method could be used for Dependency Injection. - /// - /// - public void SetTreeParameters(ITreeService treeParams) - { - this.DialogMode = treeParams.DialogMode; - this.NodeKey = treeParams.NodeKey; - this.FunctionToCall = treeParams.FunctionToCall; - this.IsDialog = treeParams.IsDialog; - this.ShowContextMenu = treeParams.ShowContextMenu; - this.id = treeParams.StartNodeID; - - if (!treeParams.ShowContextMenu) - this.RootNode.Menu = null; - } - - /// - /// Returns the tree service url to render the tree - /// - /// - public string GetTreeInitUrl() - { - TreeService treeSvc = new TreeService(this.StartNodeID, TreeAlias, null, null, TreeDialogModes.none, ""); - return treeSvc.GetInitUrl(); - } - - /// - /// Returns the tree service url to return the tree xml structure from the root node - /// - /// - public string GetTreeServiceUrl() - { - return GetTreeServiceUrl(this.StartNodeID); - } - - /// - /// Returns the tree service url to return the tree xml structure from the node passed in - /// - /// - /// - public string GetTreeServiceUrl(int id) - { - // updated by NH to pass showcontextmenu, isdialog and dialogmode variables - TreeService treeSvc = new TreeService(id, TreeAlias, this.ShowContextMenu, this.IsDialog, this.DialogMode, ""); - return treeSvc.GetServiceUrl(); - } - - /// - /// Returns the tree service url to return the tree xml structure based on a string node key. - /// - /// - /// - public string GetTreeServiceUrl(string nodeKey) - { - TreeService treeSvc = new TreeService(-1, TreeAlias, this.ShowContextMenu, this.IsDialog, this.DialogMode, "", nodeKey); - return treeSvc.GetServiceUrl(); - } - - - - /// - /// Returns the tree service url to render the tree in dialog mode - /// - /// - public virtual string GetTreeDialogUrl() - { - TreeService treeSvc = new TreeService(this.StartNodeID, TreeAlias, false, true, this.DialogMode, ""); - return treeSvc.GetServiceUrl(); - } - - /// - /// Returns the tree service url to render tree xml structure from the node passed in, in dialog mode. - /// - /// - /// - public virtual string GetTreeDialogUrl(int id) - { - TreeService treeSvc = new TreeService(id, TreeAlias, false, true, this.DialogMode, ""); - return treeSvc.GetServiceUrl(); - } - - /// - /// Returns the serialized data for the nodeId passed in. - /// - /// - /// This may not work with ITrees that don't support the BaseTree structure with TreeService. - /// If a tree implements other query string data to make it work, this may not function since - /// it only relies on the 3 parameters. - /// - /// - /// - /// - public string GetSerializedNodeData(string nodeId) - { - XmlTree xTree = new XmlTree(); - int id; - if (int.TryParse(nodeId, out id)) - this.id = id; - else - this.NodeKey = nodeId; - - this.Render(ref xTree); - - return xTree.ToString(); - } - - /// - /// Returns the default actions for a root node - /// - /// - public static List GetDefaultRootNodeActions() - { - List actions = new List(); - actions.Add(ActionNew.Instance); - actions.Add(ContextMenuSeperator.Instance); - actions.Add(ActionRefresh.Instance); - return actions; - } - - /// - /// Returns the tree header title. If the alias isn't found in the language files, then it will - /// return the title stored in the umbracoAppTree table. - /// - /// - /// - public static string GetTreeHeader(string alias) - { - string treeCaption = Current.Services.TextService.Localize(alias); - //this is a hack. the tree header title should be in the language files, however, if it is not, we're just - //going to make it equal to what is specified in the db. - if (treeCaption.Length > 0 && treeCaption.Substring(0, 1) == "[") - { - var tree = Current.Services.ApplicationTreeService.GetByAlias(alias); - if (tree != null) - return tree.Title.SplitPascalCasing().ToFirstUpperInvariant(); - } - return treeCaption; - } - - - #region Events - - //These events are poorly designed because they cannot be implemented in the tree inheritance structure, - //it would be up to the individual trees to ensure they launch the events which is not ideal. - //they are also named in appropriately in regards to standards and because everything is by ref, there is no need to - //have 2 events, makes no difference if you want to modify the contents of the data. - public delegate void BeforeNodeRenderEventHandler(ref XmlTree sender, ref XmlTreeNode node, EventArgs e); - public delegate void AfterNodeRenderEventHandler(ref XmlTree sender, ref XmlTreeNode node, EventArgs e); - public static event BeforeNodeRenderEventHandler BeforeNodeRender; - public static event AfterNodeRenderEventHandler AfterNodeRender; - - public static event EventHandler BeforeTreeRender; - public static event EventHandler AfterTreeRender; - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnBeforeNodeRender(ref XmlTree sender, ref XmlTreeNode node, EventArgs e) - { - if (sender != null && node != null) - { - if (BeforeNodeRender != null) - BeforeNodeRender(ref sender, ref node, e); - } - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnAfterNodeRender(ref XmlTree sender, ref XmlTreeNode node, EventArgs e) - { - if (AfterNodeRender != null) - AfterNodeRender(ref sender, ref node, e); - } - - protected virtual void OnBeforeTreeRender(object sender, TreeEventArgs e) - { - if (BeforeTreeRender != null) - BeforeTreeRender(sender, e); - } - - protected virtual void OnAfterTreeRender(object sender, TreeEventArgs e) - { - if (AfterTreeRender != null) - AfterTreeRender(sender, e); - } - - /// - /// Event that is raised once actions are assigned to nodes - /// - public static event EventHandler NodeActionsCreated; - - protected virtual void OnNodeActionsCreated(NodeActionsEventArgs e) - { - if (NodeActionsCreated != null) - NodeActionsCreated(this, e); - } - - #endregion - - //void IApplicationEventHandler.OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - //{ - //} - - //void IApplicationEventHandler.OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - //{ - //} - - //void IApplicationEventHandler.OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - //{ - // TreeController.TreeNodesRendering += TreeController_TreeNodesRendering; - //} - - //static void TreeController_TreeNodesRendering(TreeController sender, TreeNodesRenderingEventArgs e) - //{ - // var baseTree = new NullTree(""); - // var legacyTree = new XmlTree(); - // var toRemove = new List(); - - // foreach (var node in e.Nodes) - // { - // //make the legacy node - // var xNode = XmlTreeNode.Create(baseTree); - // xNode.HasChildren = node.HasChildren; - // xNode.IconClass = node.Icon; - // xNode.NodeID = node.NodeId; - // xNode.NodeType = sender.TreeAlias; - // xNode.Text = node.Title; - // xNode.TreeType = sender.TreeAlias; - // //we cannot support this - // //xNode.OpenIcon = node.Icon; - // //xNode.Menu = ?? - - // baseTree.OnBeforeNodeRender(ref legacyTree, ref xNode, new EventArgs()); - // //if the user has nulled this item, then we need to remove it - // if (xNode == null) - // { - // toRemove.Add(node); - // } - // else - // { - // //add to the legacy tree - this mimics what normally happened in legacy trees - // legacyTree.Add(xNode); - - // //now fire the after event - // baseTree.OnAfterNodeRender(ref legacyTree, ref xNode, new EventArgs()); - - // //ok now we need to determine if we need to map any changes back to the real node - // // these are the only properties that can be mapped back. - // node.HasChildren = xNode.HasChildren; - // node.Icon = xNode.IconClass; - // if (xNode.Icon.IsNullOrWhiteSpace() == false) - // { - // node.Icon = xNode.Icon; - // } - // node.NodeType = xNode.NodeType; - // node.Title = xNode.Text; - // } - // } - - // //now remove the nodes that were removed - // foreach (var r in toRemove) - // { - // e.Nodes.Remove(r); - // } - //} - - - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/ITreeService.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/ITreeService.cs deleted file mode 100644 index bfbbc60f59..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/ITreeService.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace umbraco.cms.presentation.Trees -{ - /// - /// All Trees rely on the properties of an ITreeService interface. This has been created to avoid having trees - /// dependant on the HttpContext - /// - public interface ITreeService - { - /// - /// The NodeKey is a string representation of the nodeID. Generally this is used for tree's whos node's unique key value is a string in instead - /// of an integer such as folder names. - /// - string NodeKey { get; } - int StartNodeID { get; } - bool ShowContextMenu { get; } - bool IsDialog { get; } - TreeDialogModes DialogMode { get; } - string FunctionToCall { get; } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NodeActionsEventArgs.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NodeActionsEventArgs.cs deleted file mode 100644 index 664f076f4c..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NodeActionsEventArgs.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.Trees -{ - public class NodeActionsEventArgs : EventArgs - { - public NodeActionsEventArgs(bool isRoot, List currActions) - { - AllowedActions = currActions; - IsRoot = isRoot; - } - - public List AllowedActions { get; private set; } - public bool IsRoot { get; private set; } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NullTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NullTree.cs deleted file mode 100644 index 1767b4c5e3..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NullTree.cs +++ /dev/null @@ -1,62 +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 System.Collections.Generic; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// An empty tree with no functionality. This gets loaded when the requested tree cannot be loaded with the type specified. - /// Should not be used directly in code. - /// - public class NullTree : BaseTree - { - - public NullTree(string application) : base(application) { } - - protected override void CreateRootNodeActions(ref List actions) - { - actions.Clear(); - actions.Add(ActionRefresh.Instance); - } - - public override void RenderJS(ref System.Text.StringBuilder Javascript) { } - - public override void Render(ref XmlTree tree) - { - XmlTreeNode xNode = XmlTreeNode.Create(this); - xNode.Text = "Error"; - xNode.Menu = null; - - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) - { - tree.Add(xNode); - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); - } - - } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.Menu = null; - rootNode.Text = "Error"; - } - - public override string TreeAlias - { - get - { - return "NullTree"; - } - } - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinition.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinition.cs deleted file mode 100644 index 5855775b9a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinition.cs +++ /dev/null @@ -1,110 +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.Core.Models; - -namespace umbraco.cms.presentation.Trees -{ - - /// - /// Defines the entire structure of an application tree including it's Type, a reference to it's ApplicationTree object, and a reference - /// to it's Application object. Tree Definitions are based on defining a database in the umbracoAppTree database. Any tree defined in this table - /// that is of an ITree type, it will be found and can be instantiated by this class. Any ITree that is not defined in the database will - /// need to be instantiated with it's own tree constructor. - /// - public class TreeDefinition - { - /// - /// Initializes a new instance of the class. - /// - /// The type. - /// The tree. - /// The app. - public TreeDefinition(Type type, ApplicationTree tree, Section app) - { - m_treeType = type; - m_tree = tree; - m_app = app; - } - - private Type m_treeType; - private ApplicationTree m_tree; - private Section m_app; - - /// - /// Returns a new instance of a BaseTree based on this Tree Definition - /// - public BaseTree CreateInstance() - { - //create the tree instance - var typeInstance = CreateTreeInstance(m_treeType, m_app.Alias); - - if (typeInstance != null) - { - //convert to BaseTree - return typeInstance; - } - return null; - } - - /// - /// Gets or sets the type of the tree. - /// - /// The type of the tree. - public Type TreeType - { - get { return m_treeType; } - set { m_treeType = value; } - } - - /// - /// Gets or sets the tree. - /// - /// The tree. - public ApplicationTree Tree - { - get { return m_tree; } - set { m_tree = value; } - } - - /// - /// Gets or sets the application. - /// - /// The app. - public Section App - { - get { return m_app; } - set { m_app = value; } - } - - /// - /// Creates an ITree instance. - /// - /// The tree. - /// The app alias. - /// - public static BaseTree CreateTreeInstance(Type tree, string appAlias) - { - BaseTree typeInstance; - //call the correct constructor - if (typeof(BaseTree).IsAssignableFrom(tree)) - typeInstance = Activator.CreateInstance(tree, new object[] { appAlias }) as BaseTree; //the BaseTree constructor - else - typeInstance = CreateTreeInstance(tree); - return typeInstance; - } - - private static BaseTree CreateTreeInstance(Type tree) - { - BaseTree typeInstance = Activator.CreateInstance(tree) as BaseTree; //an empty constructor (ITree) - return typeInstance; - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs deleted file mode 100644 index 9157080597..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Web; -using Umbraco.Web.Composing; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// A collection of TreeDefinitions found in any loaded assembly. - /// - public class TreeDefinitionCollection : List - { - - //create singleton - private static readonly TreeDefinitionCollection instance = new TreeDefinitionCollection(); - - private static readonly object Locker = new object(); - private static volatile bool _ensureTrees = false; - - public static TreeDefinitionCollection Instance - { - get - { - instance.EnsureTreesRegistered(); - return instance; - } - } - - /// - /// Find the TreeDefinition object based on the ITree - /// - /// - /// - public TreeDefinition FindTree(BaseTree tree) - { - EnsureTreesRegistered(); - - var foundTree = this.Find( - t => t.TreeType == tree.GetType() - ); - if (foundTree != null) - return foundTree; - - return null; - } - - /// - /// Finds the TreeDefinition with the generic type specified - /// - /// - /// - public TreeDefinition FindTree() where T : BaseTree - { - EnsureTreesRegistered(); - - var foundTree = this.Find( - delegate(TreeDefinition t) - { - // zb-00002 #29929 : use IsAssignableFrom instead of Equal, otherwise you can't override built-in - // trees because for ex. PermissionEditor.aspx.cs OnInit calls FindTree() - return typeof(T).IsAssignableFrom(t.TreeType); - } - ); - if (foundTree != null) - return foundTree; - - return null; - } - - /// - /// Return the TreeDefinition object based on the tree alias and application it belongs to - /// - /// - /// - public TreeDefinition FindTree(string alias) - { - EnsureTreesRegistered(); - - var foundTree = this.Find( - t => t.Tree.Alias.ToLower() == alias.ToLower() - ); - if (foundTree != null) - return foundTree; - - return null; - } - - /// - /// Return a list of TreeDefinition's with the appAlias specified - /// - /// - /// - public List FindTrees(string appAlias) - { - EnsureTreesRegistered(); - - return this.FindAll( - tree => (tree.App != null && tree.App.Alias.ToLower() == appAlias.ToLower()) - ); - } - - /// - /// Return a list of TreeDefinition's with the appAlias specified - /// - /// - /// - public List FindActiveTrees(string appAlias) - { - EnsureTreesRegistered(); - - return this.FindAll( - tree => (tree.App != null && tree.App.Alias.ToLower() == appAlias.ToLower() && tree.Tree.Initialize) - ); - } - - public void ReRegisterTrees() - { - //clears the trees/flag so that they are lazily refreshed on next access - lock (Locker) - { - this.Clear(); - _ensureTrees = false; - } - } - - /// - /// Finds all instances of ITree in loaded assemblies, then finds their associated ApplicationTree and Application objects - /// and stores them together in a TreeDefinition class and adds the definition to our list. - /// This will also store an instance of each tree object in the TreeDefinition class which should be - /// used when referencing all tree classes. - /// - private void EnsureTreesRegistered() - { - if (_ensureTrees == false) - { - lock (Locker) - { - if (_ensureTrees == false) - { - - var foundITrees = Current.TypeLoader.GetTrees(); - - var appTrees = Current.Services.ApplicationTreeService.GetAll().ToList(); - var apps = Current.Services.SectionService.GetSections().ToList(); - - foreach (var type in foundITrees) - { - - //find the Application tree's who's combination of assembly name and tree type is equal to - //the Type that was found's full name. - //Since a tree can exist in multiple applications we'll need to register them all. - - //The logic of this has changed in 6.0: http://issues.umbraco.org/issue/U4-1360 - // we will support the old legacy way but the normal way is to match on assembly qualified names - - var appTreesForType = appTrees.FindAll( - tree => - { - //match the type on assembly qualified name - return tree.GetRuntimeType() == type; - } - ); - - foreach (var appTree in appTreesForType) - { - //find the Application object whos name is the same as our appTree ApplicationAlias - var app = apps.Find( - a => (a.Alias == appTree.ApplicationAlias) - ); - - var def = new TreeDefinition(type, appTree, app); - this.Add(def); - } - } - //sort our trees with the sort order definition - this.Sort((t1, t2) => t1.Tree.SortOrder.CompareTo(t2.Tree.SortOrder)); - - _ensureTrees = true; - } - } - } - - - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDialogModes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDialogModes.cs deleted file mode 100644 index fab8545d76..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDialogModes.cs +++ /dev/null @@ -1,17 +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; - -namespace umbraco.cms.presentation.Trees -{ - public enum TreeDialogModes - { - none,id,locallink,fulllink - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeEventArgs.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeEventArgs.cs deleted file mode 100644 index 2f6f4ecf5e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeEventArgs.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - - -namespace umbraco.cms.presentation.Trees -{ - /// - /// - /// - public class TreeEventArgs : EventArgs - { - /// - /// Gets the tree. - /// - /// The tree. - public XmlTree Tree { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The tree which the event is for. - public TreeEventArgs(XmlTree tree) - { - this.Tree = tree; - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeRequestParams.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeRequestParams.cs deleted file mode 100644 index 9f28a472d0..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeRequestParams.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// An ITreeService class that returns the values found in the Query String or any dictionary - /// - internal class TreeRequestParams : ITreeService - { - private TreeRequestParams() { } - - private Dictionary m_params; - - public static TreeRequestParams FromQueryStrings() - { - Dictionary p = new Dictionary(); - foreach (string key in HttpContext.Current.Request.QueryString.Keys) - { - p.Add(key, HttpUtility.HtmlEncode(HttpContext.Current.Request.QueryString[key])); - //p.Add(item.Key.ToString(), item.Value.ToString()); - } - return FromDictionary(p); - } - - public static TreeRequestParams FromDictionary(Dictionary items) - { - TreeRequestParams treeParams = new TreeRequestParams(); - treeParams.m_params = items; - return treeParams; - } - - /// - /// Converts the tree parameters to a tree service object - /// - /// - public TreeService CreateTreeService() - { - return new TreeService() - { - ShowContextMenu = this.ShowContextMenu, - IsDialog = this.IsDialog, - DialogMode = this.DialogMode, - App = this.Application, - TreeType = this.TreeType, - NodeKey = this.NodeKey, - StartNodeID = this.StartNodeID, - FunctionToCall = this.FunctionToCall - }; - } - - public string NodeKey - { - get - { - return (m_params.ContainsKey("nodeKey") ? m_params["nodeKey"] : ""); - } - } - public string Application - { - get - { - return (m_params.ContainsKey("app") ? m_params["app"] : m_params.ContainsKey("appAlias") ? m_params["appAlias"] : ""); - } - } - public int StartNodeID - { - get - { - if(m_params.ContainsKey("id")) - { - int sNodeID; - if (int.TryParse(m_params["id"], out sNodeID)) - return sNodeID; - } - return -1; - } - } - public string FunctionToCall - { - get - { - return (m_params.ContainsKey("functionToCall") ? m_params["functionToCall"] : ""); - } - } - public bool IsDialog - { - get - { - bool value; - if (m_params.ContainsKey("isDialog")) - if (bool.TryParse(m_params["isDialog"], out value)) - return value; - return false; - } - } - public TreeDialogModes DialogMode - { - get - { - if (m_params.ContainsKey("dialogMode") && IsDialog) - { - try - { - return (TreeDialogModes)Enum.Parse(typeof(TreeDialogModes), m_params["dialogMode"]); - } - catch - { - return TreeDialogModes.none; - } - } - return TreeDialogModes.none; - } - } - public bool ShowContextMenu - { - get - { - bool value; - if (m_params.ContainsKey("contextMenu")) - if (bool.TryParse(m_params["contextMenu"], out value)) - return value; - return true; - } - } - public string TreeType - { - get - { - return (m_params.ContainsKey("treeType") ? m_params["treeType"] : ""); - } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeService.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeService.cs deleted file mode 100644 index 6a4df9ea0a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.Text; -using Umbraco.Web._Legacy.Controls; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// A utility class to aid in creating the URL for returning XML for a tree structure and - /// for reading the parameters from the URL when a request is made. - /// - public class TreeService : TreeUrlGenerator, ITreeService - { - - /// - /// Default empty constructor - /// - public TreeService() : base() { } - - /// - /// Constructor to assign all TreeService properties except nodeKey in one call - /// - /// - /// - /// - /// - /// - /// - public TreeService(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app) - { - StartNodeID = startNodeID ?? -1; - TreeType = treeType; - ShowContextMenu = showContextMenu ?? true; - IsDialog = isDialog ?? false; - m_dialogMode = dialogMode; - App = app; - } - - /// - /// Constructor to assign all TreeService properties in one call - /// - /// - /// - /// - /// - /// - /// - /// - public TreeService(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app, string nodeKey) - { - StartNodeID = startNodeID ?? -1; - TreeType = treeType; - ShowContextMenu = showContextMenu ?? true; - IsDialog = isDialog ?? false; - m_dialogMode = dialogMode; - App = app; - NodeKey = nodeKey; - } - - public TreeService(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app, string nodeKey, string functionToCall) - { - StartNodeID = startNodeID ?? -1; - TreeType = treeType; - ShowContextMenu = showContextMenu ?? true; - IsDialog = isDialog ?? false; - m_dialogMode = dialogMode; - App = app; - NodeKey = nodeKey; - FunctionToCall = functionToCall; - } - - private TreeDialogModes m_dialogMode; - - #region Public Properties - - - public TreeDialogModes DialogMode - { - get { return m_dialogMode; } - set { m_dialogMode = value; } - } - - #endregion - - /// - /// Static method to return the tree service url with the specified parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static string GetServiceUrl(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app, string nodeKey, string functionToCall) - { - TreeService treeSvc = new TreeService(startNodeID, treeType, showContextMenu, isDialog, dialogMode, app, nodeKey, functionToCall); - return treeSvc.GetServiceUrl(); - } - - /// - /// static method to return the tree init url with the specified parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static string GetInitUrl(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app, string nodeKey, string functionToCall) - { - TreeService treeSvc = new TreeService(startNodeID, treeType, showContextMenu, isDialog, dialogMode, app, nodeKey, functionToCall); - return treeSvc.GetInitUrl(); - } - - protected override string GetUrl(string pageUrl) - { - StringBuilder sb = new StringBuilder(base.GetUrl(pageUrl)); - if (this.DialogMode != TreeDialogModes.none) sb.Append(string.Format("&dialogMode={0}", this.DialogMode.ToString())); - return sb.ToString(); - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeUrlGenerator.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeUrlGenerator.cs deleted file mode 100644 index 277c6270a1..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeUrlGenerator.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Text; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// This class will generate the URLs for iframe tree pages. - /// Generally used to get the a tree picker url. - /// - /// - /// This was created in 4.1 so that this helper class can be exposed to other assemblies since - /// it only existed in the presentation assembly in previous versions - /// - public class TreeUrlGenerator - { - - public const string TREE_URL = "tree.aspx"; - public const string INIT_URL = "treeinit.aspx"; - public const string PICKER_URL = "treepicker.aspx"; - - private int? m_startNodeID; - private string m_treeType; - private bool? m_showContextMenu; - private bool? m_isDialog; - private string m_app; - private string m_nodeKey; - private string m_functionToCall; - - #region Public Properties - - public string FunctionToCall - { - get { return m_functionToCall; } - set { m_functionToCall = value; } - } - - public string NodeKey - { - get { return m_nodeKey; } - set { m_nodeKey = value; } - } - - public int StartNodeID - { - get { return m_startNodeID ?? -1; } - set { m_startNodeID = value; } - } - - public string TreeType - { - get { return m_treeType; } - set { m_treeType = value; } - } - - public bool ShowContextMenu - { - get { return m_showContextMenu ?? true; } - set { m_showContextMenu = value; } - } - - public bool IsDialog - { - get { return m_isDialog ?? false; } - set { m_isDialog = value; } - } - - public string App - { - get { return m_app; } - set { m_app = value; } - } - #endregion - - /// - /// Returns the url for servicing the xml tree request based on the parameters specified on this class. - /// - /// Tree service url as a string - public string GetServiceUrl() - { - return Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco) + "/" + GetUrl(TREE_URL); - } - - /// - /// Static method to return the tree service url with the specified parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static string GetServiceUrl(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, string app, string nodeKey, string functionToCall) - { - TreeUrlGenerator treeSvc = new TreeUrlGenerator() - { - StartNodeID = startNodeID ?? -1, - TreeType = treeType, - ShowContextMenu = showContextMenu ?? true, - IsDialog = isDialog ?? false, - App = app, - NodeKey = nodeKey, - FunctionToCall = functionToCall - }; - return treeSvc.GetServiceUrl(); - } - - /// - /// Returns the url for initializing the tree based on the parameters specified on this class - /// - /// - public string GetInitUrl() - { - return Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco) + "/" + GetUrl(INIT_URL); - } - - /// - /// static method to return the tree init url with the specified parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static string GetInitUrl(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, string app, string nodeKey, string functionToCall) - { - TreeUrlGenerator treeSvc = new TreeUrlGenerator() - { - StartNodeID = startNodeID ?? -1, - TreeType = treeType, - ShowContextMenu = showContextMenu ?? true, - IsDialog = isDialog ?? false, - App = app, - NodeKey = nodeKey, - FunctionToCall = functionToCall - }; - return treeSvc.GetInitUrl(); - } - - /// - /// Returns the url for the tree picker (used on modal windows) based on the parameters specified on this class - /// - public static string GetPickerUrl(string app, string treeType) - { - TreeUrlGenerator treeSvc = new TreeUrlGenerator(); - treeSvc.App = app; - treeSvc.TreeType = treeType; - return treeSvc.GetPickerUrl(); - } - - /// - /// Returns the url for the tree picker (used on modal windows) based on the parameters specified on this class - /// - public string GetPickerUrl() - { - return Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco) + "/dialogs/" + GetUrl(PICKER_URL); - } - - /// - /// Generates the URL parameters for the tree service. - /// - /// the base url (i.e. tree.aspx) - /// - protected virtual string GetUrl(string pageUrl) - { - StringBuilder sb = new StringBuilder(); - - sb.Append(pageUrl); - //insert random - sb.Append(string.Format("?rnd={0}", Guid.NewGuid().ToString("N"))); - - sb.Append(string.Format("&id={0}", this.StartNodeID.ToString())); - if (!string.IsNullOrEmpty(this.TreeType)) sb.Append(string.Format("&treeType={0}", this.TreeType)); - if (!string.IsNullOrEmpty(this.NodeKey)) sb.Append(string.Format("&nodeKey={0}", this.NodeKey)); - sb.Append(string.Format("&contextMenu={0}", this.ShowContextMenu.ToString().ToLower())); - sb.Append(string.Format("&isDialog={0}", this.IsDialog.ToString().ToLower())); - if (!string.IsNullOrEmpty(this.App)) sb.Append(string.Format("&app={0}", this.App)); - - return sb.ToString(); - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs deleted file mode 100644 index 56f794caa0..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs +++ /dev/null @@ -1,455 +0,0 @@ -using System.Xml.Serialization; -using System.Collections; -using System; -using System.Xml.Schema; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Umbraco.Web.Composing; -using Umbraco.Web.UI.Pages; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// Used for serializing data to XML as the data structure for the JavaScript tree - /// - [XmlRoot(ElementName = "tree", IsNullable = false), Serializable] - public class XmlTree - { - - public XmlTree() - { - Init(); - } - - private void Init() - { - } - - public void Add(XmlTreeNode obj) - { - treeCollection.Add(obj); - } - - [XmlIgnore] - public XmlTreeNode this[int index] - { - get { return (XmlTreeNode)treeCollection[index]; } - } - - [XmlIgnore] - public int Count - { - get { return treeCollection.Count; } - } - - public void Clear() - { - treeCollection.Clear(); - } - - public XmlTreeNode Remove(int index) - { - XmlTreeNode obj = treeCollection[index]; - treeCollection.Remove(obj); - return obj; - } - - public void Remove(XmlTreeNode obj) - { - treeCollection.Remove(obj); - } - - - private List __treeCollection; - - [XmlElement(Type = typeof(XmlTreeNode), ElementName = "tree", IsNullable = false, Form = XmlSchemaForm.Qualified)] - public List treeCollection - { - get - { - if (__treeCollection == null) __treeCollection = new List(); - return __treeCollection; - } - set { __treeCollection = value; } - } - - [System.Runtime.InteropServices.DispIdAttribute(-4)] - public IEnumerator GetEnumerator() - { - return (treeCollection as IEnumerable).GetEnumerator(); - } - - } - - /// - /// Used for serializing data to XML as the data structure for the JavaScript tree - /// - [Serializable] - public class XmlTreeNode : IXmlSerializable - { - - private XmlTreeNode() - { - m_nodeStyle = new NodeStyle(); - } - - /// - /// creates a new XmlTreeNode with the default parameters from the BaseTree - /// - /// - /// - public static XmlTreeNode Create(BaseTree bTree) - { - XmlTreeNode xNode = new XmlTreeNode(); - xNode.Menu = bTree.AllowedActions.FindAll(delegate(IAction a) { return true; }); //return a duplicate copy of the list - xNode.NodeType = bTree.TreeAlias; - xNode.Source = string.Empty; - xNode.IsRoot = false; - //generally the tree type and node type are the same but in some cased they are not. - xNode.m_treeType = bTree.TreeAlias; - return xNode; - } - - /// - /// creates a new XmlTreeNode with the default parameters for the BaseTree root node - /// - /// - /// - public static XmlTreeNode CreateRoot(BaseTree bTree) - { - XmlTreeNode xNode = new XmlTreeNode(); - xNode.NodeID = bTree.StartNodeID.ToString(); - xNode.Source = bTree.GetTreeServiceUrl(); - xNode.Menu = bTree.RootNodeActions.FindAll(delegate(IAction a) { return true; }); //return a duplicate copy of the list - xNode.NodeType = bTree.TreeAlias; - xNode.Text = BaseTree.GetTreeHeader(bTree.TreeAlias); - - // The apps dashboard action will be used - xNode.Action = "javascript:" + ClientTools.Scripts.OpenDashboard(bTree.app); - - xNode.IsRoot = true; - //generally the tree type and node type are the same but in some cased they are not. - xNode.m_treeType = bTree.TreeAlias; - return xNode; - } - - private NodeStyle m_nodeStyle; - - private bool? m_notPublished; - private bool? m_isProtected; - private List m_menu; - private string m_text; - private string m_action; - [Obsolete("This is never used. From version 3 and below probably")] - private string m_rootSrc; - private string m_src; - private string m_iconClass = ""; - private string m_icon; - private string m_openIcon; - private string m_nodeType; - private string m_nodeID; - private string m_treeType; - - /// - /// Set to true when a node is created with CreateRootNode - /// - internal bool IsRoot { get; private set; } - - /// - /// Generally the tree type and node type are the same but in some cased they are not so - /// we need to store the tree type too which is read only. - /// - public string TreeType - { - get { return m_treeType; } - internal set { m_treeType = value; } - } - - public bool HasChildren - { - get - { - return m_HasChildren ?? !string.IsNullOrEmpty(this.Source); //defaults to true if source is specified - } - set - { - m_HasChildren = value; - } - } - private bool? m_HasChildren = null; - - public string NodeID - { - get { return m_nodeID; } - set { m_nodeID = value; } - } - - /// - /// The tree node text - /// - public string Text - { - get { return m_text; } - set { m_text = value; } - } - - /// - /// The CSS class of the icon to use for the node - /// - public string IconClass - { - get { return m_iconClass; } - set { m_iconClass = value; } - } - - /// - /// The JavaScript action for the node - /// - public string Action - { - get { return m_action; } - set { m_action = value; } - } - - /// - /// A string of letters representing actions for the context menu - /// - public List Menu - { - get { return m_menu; } - set { m_menu = value; } - } - - /// - /// The xml source for the child nodes (a URL) - /// - public string Source - { - get { return m_src; } - set { m_src = value; } - } - - /// - /// The path to the icon to display for the node - /// - public string Icon - { - get { return m_icon; } - set { m_icon = value; } - } - - /// - /// The path to the icon to display for the node if the node is showing it's children - /// - public string OpenIcon - { - get { return m_openIcon; } - set { m_openIcon = value; } - } - - /// - /// Normally just the type of tree being rendered. - /// This should really only be set with this property in very special cases - /// where the create task for a node in the same tree as another node is different. - /// - public string NodeType - { - get { return m_nodeType; } - set { m_nodeType = value; } - } - - - - /// - /// Returns the styling object used to add common styles to a node - /// - public NodeStyle Style - { - get { return m_nodeStyle; } - } - - /// - /// Used to add common styles to an XmlTreeNode. - /// This also adds the ability to add a custom class which will add the class to the li node - /// that is rendered in the tree whereas the IconClass property of the XmlTreeNode object - /// adds a class to the anchor of the li node. - /// - public sealed class NodeStyle - { - internal NodeStyle() - { - AppliedClasses = new List(); - } - - private const string DimNodeCssClass = "not-published"; - private const string HighlightNodeCssClass = "has-unpublished-version"; - private const string SecureNodeCssClass = "protected"; - - internal List AppliedClasses { get; private set; } - - /// - /// Dims the color of the node - /// - public void DimNode() - { - if (!AppliedClasses.Contains(DimNodeCssClass)) - AppliedClasses.Add(DimNodeCssClass); - } - - /// - /// Adds the star icon highlight overlay to a node - /// - public void HighlightNode() - { - if (!AppliedClasses.Contains(HighlightNodeCssClass)) - AppliedClasses.Add(HighlightNodeCssClass); - } - - /// - /// Adds the padlock icon overlay to a node - /// - public void SecureNode() - { - if (!AppliedClasses.Contains(SecureNodeCssClass)) - AppliedClasses.Add(SecureNodeCssClass); - } - - /// - /// Adds a custom class to the li node of the tree - /// - /// - public void AddCustom(string cssClass) - { - if (!AppliedClasses.Contains(cssClass)) - AppliedClasses.Add(cssClass); - } - } - - - #region IXmlSerializable Members - - public XmlSchema GetSchema() - { - return null; - } - - public void ReadXml(System.Xml.XmlReader reader) - { - if (reader.HasAttributes) - { - while (reader.MoveToNextAttribute()) - { - //try to parse the name into enum - TreeAttributes current; - try - { - current = (TreeAttributes)Enum.Parse(typeof(TreeAttributes), reader.Name, true); - } - catch - { - break; - } - switch (current) - { - case TreeAttributes.nodeID: - this.m_nodeID = reader.Value; - break; - case TreeAttributes.text: - this.m_text = reader.Value; - break; - case TreeAttributes.iconClass: - this.m_iconClass = reader.Value; - break; - case TreeAttributes.action: - this.m_action = reader.Value; - break; - case TreeAttributes.menu: - this.m_menu = !string.IsNullOrEmpty(reader.Value) - ? FromActionSymbols(reader.Value.Split(',')).ToList() - : null; - break; - case TreeAttributes.rootSrc: - this.m_rootSrc = reader.Value; - break; - case TreeAttributes.src: - this.m_src = reader.Value; - break; - case TreeAttributes.icon: - this.m_icon = reader.Value; - break; - case TreeAttributes.openIcon: - this.m_openIcon = reader.Value; - break; - case TreeAttributes.nodeType: - this.m_nodeType = reader.Value; - break; - case TreeAttributes.notPublished: - if (!string.IsNullOrEmpty(reader.Value)) this.m_notPublished = bool.Parse(reader.Value); - break; - case TreeAttributes.isProtected: - if (!string.IsNullOrEmpty(reader.Value)) this.m_isProtected = bool.Parse(reader.Value); - break; - case TreeAttributes.hasChildren: - if (!string.IsNullOrEmpty(reader.Value)) this.HasChildren = bool.Parse(reader.Value); - break; - } - } - //need to set the hasChildren property if it is null but there is a source - //this happens when the hasChildren attribute is not set because the developer didn't know it was there - //only occurs for ITree obviously - if (!this.HasChildren && !string.IsNullOrEmpty(this.m_src)) - this.HasChildren = true; - - } - - reader.Read(); - } - - public void WriteXml(System.Xml.XmlWriter writer) - { - writer.WriteAttributeString(TreeAttributes.nodeID.ToString(), m_nodeID); - writer.WriteAttributeString(TreeAttributes.text.ToString(), m_text); - writer.WriteAttributeString(TreeAttributes.iconClass.ToString(), m_iconClass); - writer.WriteAttributeString(TreeAttributes.action.ToString(), m_action); - writer.WriteAttributeString(TreeAttributes.menu.ToString(), m_menu != null && m_menu.Count > 0 ? string.Join(",", ToActionSymbols(m_menu)) : ""); - writer.WriteAttributeString(TreeAttributes.rootSrc.ToString(), m_rootSrc); - writer.WriteAttributeString(TreeAttributes.src.ToString(), m_src); - writer.WriteAttributeString(TreeAttributes.icon.ToString(), m_icon); - writer.WriteAttributeString(TreeAttributes.openIcon.ToString(), m_openIcon); - writer.WriteAttributeString(TreeAttributes.nodeType.ToString(), m_nodeType); - writer.WriteAttributeString(TreeAttributes.hasChildren.ToString(), HasChildren.ToString().ToLower()); - if (m_notPublished.HasValue) writer.WriteAttributeString(TreeAttributes.notPublished.ToString(), m_notPublished.Value.ToString().ToLower()); - if (m_isProtected.HasValue) writer.WriteAttributeString(TreeAttributes.isProtected.ToString(), m_isProtected.Value.ToString().ToLower()); - } - - internal enum TreeAttributes - { - nodeID, text, iconClass, action, menu, rootSrc, src, icon, openIcon, nodeType, notPublished, isProtected, hasChildren - } - - #endregion - - /// - /// This method will return a list of IAction's based on a string (letter) list. Each character in the list may represent - /// an IAction. This will associate any found IActions based on the Letter property of the IAction with the character being referenced. - /// - /// - /// returns a list of actions that have an associated letter found in the action string list - private IEnumerable FromActionSymbols(IEnumerable actions) - { - return Current.Actions.GetByLetters(actions); - } - - /// - /// Returns the string (letter) representation of the actions that make up the actions collection - /// - /// - private IEnumerable ToActionSymbols(IEnumerable actions) - { - return actions.Select(x => x.Letter.ToString(CultureInfo.InvariantCulture)).ToArray(); - } - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs deleted file mode 100644 index d07251ea7a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Web.UI; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Web; -using Umbraco.Web.UI.JavaScript; -using Umbraco.Web._Legacy.Controls; -using Umbraco.Web.UI.Pages; - -namespace umbraco.presentation.developer.packages -{ - /// - /// Summary description for packager. - /// - [Obsolete("This should not be used and will be removed in v8, this is kept here only for backwards compat reasons, this page should never be rendered/used")] - public class Installer : UmbracoEnsuredPage - { - - private Control _configControl; - private readonly cms.businesslogic.packager.Installer _installer; - protected Pane pane_installing; - protected Pane pane_optional; - - public Installer() - { - CurrentApp = Constants.Applications.Packages; - _installer = new cms.businesslogic.packager.Installer(Security.CurrentUser.Id); - } - - protected void Page_Load(object sender, EventArgs e) - { - if (string.IsNullOrEmpty(Request.GetItemAsString("installing"))) - return; - - pane_optional.Visible = false; - pane_installing.Visible = true; - ProcessInstall(Request.GetItemAsString("installing")); - } - - private void ProcessInstall(string currentStep) - { - var dir = Request.GetItemAsString("dir"); - - int.TryParse(Request.GetItemAsString("pId"), out var packageId); - - switch (currentStep.ToLowerInvariant()) - { - case "custominstaller": - var customControl = Request.GetItemAsString("customControl"); - - if (customControl.IsNullOrWhiteSpace() == false) - { - pane_optional.Visible = false; - - _configControl = LoadControl(SystemDirectories.Root + customControl); - _configControl.ID = "packagerConfigControl"; - - pane_optional.Controls.Add(_configControl); - pane_optional.Visible = true; - - if (IsPostBack == false) - { - //We still need to clean everything up which is normally done in the Finished Action - PerformPostInstallCleanup(packageId, dir); - } - - } - else - { - //if the custom installer control is empty here (though it should never be because we've already checked for it previously) - //then we should run the normal FinishedAction - PerformFinishedAction(packageId, dir); - } - break; - default: - break; - } - } - - private void PerformPostInstallCleanup(int packageId, string dir) - { - _installer.InstallCleanUp(packageId, dir); - - // Update ClientDependency version - var clientDependencyConfig = new ClientDependencyConfiguration(Logger); - var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( - UmbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); - - //clear the tree cache - we'll do this here even though the browser will reload, but just in case it doesn't can't hurt. - ClientTools.ClearClientTreeCache().RefreshTree("packager"); - TreeDefinitionCollection.Instance.ReRegisterTrees(); - } - - private void PerformFinishedAction(int packageId, string dir) - { - pane_optional.Visible = false; - PerformPostInstallCleanup(packageId, dir); - } - } -} From a100d321f67ec2640124a5922684f7613d09396f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 18:08:57 +1100 Subject: [PATCH 109/585] Fixes group node route --- .../common/directives/components/tree/umbtree.directive.js | 2 ++ src/Umbraco.Web/Models/Trees/SectionRootNode.cs | 5 +++-- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) 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 3212690bbb..836a5af2a6 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 @@ -80,6 +80,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use // entire tree again since we already still have it in memory. Of course if the section is different we will // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times // since it saves on data retreival and DOM processing. + //TODO: This isn't used!? var lastSection = ""; /** Helper function to emit tree events */ @@ -91,6 +92,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } } + //TODO: This isn't used!? function clearCache(section) { treeService.clearCache({ section: section }); } diff --git a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs index 4265cbaa7f..cd4fc3e483 100644 --- a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs +++ b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs @@ -34,12 +34,13 @@ namespace Umbraco.Web.Models.Trees ///
/// /// - public static TreeRootNode CreateGroupNode(TreeNodeCollection children) + public static TreeRootNode CreateGroupNode(TreeNodeCollection children, string section) { var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) { IsGroup = true, - Children = children + Children = children, + RoutePath = section }; return sectionRoot; diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index b4d217fe62..fd669a1b07 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -133,7 +133,7 @@ namespace Umbraco.Web.Trees if (groupNodeCollection.Count > 0) { - var groupRoot = TreeRootNode.CreateGroupNode(groupNodeCollection); + var groupRoot = TreeRootNode.CreateGroupNode(groupNodeCollection, application); groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); rootNodeGroups.Add(groupRoot); From f868172f484c968e4036658761abae3771aeb3ec Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 16 Oct 2018 10:58:17 +0200 Subject: [PATCH 110/585] Cleanup --- .../PublishedContent/PublishedModelFactory.cs | 4 ++-- src/Umbraco.Core/ReflectionUtilities.cs | 18 +++++++-------- .../CtorInvokeBenchmarks.cs | 2 +- .../Clr/ReflectionUtilitiesTests.cs | 22 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs index c72a89c1f2..67758c1c69 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -71,7 +71,7 @@ namespace Umbraco.Core.Models.PublishedContent throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); // have to use an unsafe ctor because we don't know the types, really - var modelCtor = ReflectionUtilities.EmitCtorUnsafe>(constructor); + var modelCtor = ReflectionUtilities.EmitConstructorUnsafe>(constructor); modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = modelCtor }; modelTypeMap[typeName] = type; } @@ -112,7 +112,7 @@ namespace Umbraco.Core.Models.PublishedContent if (ctor != null) return ctor(); var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); - ctor = modelInfo.ListCtor = ReflectionUtilities.EmitCtor>(declaring: listType); + ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstuctor>(declaring: listType); return ctor(); } diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index a5acfe78e3..870cb9ec13 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -295,7 +295,7 @@ namespace Umbraco.Core /// Occurs when the constructor does not exist and is true. /// Occurs when is not a Func or when /// is specified and does not match the function's returned type. - public static TLambda EmitCtor(bool mustExist = true, Type declaring = null) + public static TLambda EmitConstuctor(bool mustExist = true, Type declaring = null) { var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); @@ -313,7 +313,7 @@ namespace Umbraco.Core } // emit - return EmitCtorSafe(lambdaParameters, lambdaReturned, ctor); + return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); } /// @@ -325,16 +325,16 @@ namespace Umbraco.Core /// Occurs when is not a Func or when its generic /// arguments do not match those of . /// Occurs when is null. - public static TLambda EmitCtor(ConstructorInfo ctor) + public static TLambda EmitConstructor(ConstructorInfo ctor) { if (ctor == null) throw new ArgumentNullException(nameof(ctor)); var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); - return EmitCtorSafe(lambdaParameters, lambdaReturned, ctor); + return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); } - private static TLambda EmitCtorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) + private static TLambda EmitConstructorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) { // get type and args var ctorDeclaring = ctor.DeclaringType; @@ -350,7 +350,7 @@ namespace Umbraco.Core ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); // emit - return EmitCtor(ctorDeclaring, ctorParameters, ctor); + return EmitConstructor(ctorDeclaring, ctorParameters, ctor); } /// @@ -367,17 +367,17 @@ namespace Umbraco.Core /// Occurs when is not a Func or when its generic /// arguments do not match those of . /// Occurs when is null. - public static TLambda EmitCtorUnsafe(ConstructorInfo ctor) + public static TLambda EmitConstructorUnsafe(ConstructorInfo ctor) { if (ctor == null) throw new ArgumentNullException(nameof(ctor)); var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); // emit - unsafe - use lambda's args and assume they are correct - return EmitCtor(lambdaReturned, lambdaParameters, ctor); + return EmitConstructor(lambdaReturned, lambdaParameters, ctor); } - private static TLambda EmitCtor(Type declaring, Type[] lambdaParameters, ConstructorInfo ctor) + private static TLambda EmitConstructor(Type declaring, Type[] lambdaParameters, ConstructorInfo ctor) { // gets the method argument types var ctorParameters = GetParameters(ctor); diff --git a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs index 4855b161df..1d5876187b 100644 --- a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs @@ -144,7 +144,7 @@ namespace Umbraco.Tests.Benchmarks // however, unfortunately, the generated "compiled to delegate" code cannot access private stuff :( - _emittedCtor = ReflectionUtilities.EmitCtor>(); + _emittedCtor = ReflectionUtilities.EmitConstuctor>(); } public IFoo IlCtor(IFoo foo) diff --git a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs index 1f7f164b21..46dae8bcfd 100644 --- a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs +++ b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs @@ -13,16 +13,16 @@ namespace Umbraco.Tests.Clr [Test] public void EmitCtorEmits() { - var ctor1 = ReflectionUtilities.EmitCtor>(); + var ctor1 = ReflectionUtilities.EmitConstuctor>(); Assert.IsInstanceOf(ctor1()); - var ctor2 = ReflectionUtilities.EmitCtor>(declaring: typeof(Class1)); + var ctor2 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class1)); Assert.IsInstanceOf(ctor2()); - var ctor3 = ReflectionUtilities.EmitCtor>(); + var ctor3 = ReflectionUtilities.EmitConstuctor>(); Assert.IsInstanceOf(ctor3(42)); - var ctor4 = ReflectionUtilities.EmitCtor>(declaring: typeof(Class3)); + var ctor4 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class3)); Assert.IsInstanceOf(ctor4(42)); } @@ -30,40 +30,40 @@ namespace Umbraco.Tests.Clr public void EmitCtorEmitsFromInfo() { var ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); - var ctor1 = ReflectionUtilities.EmitCtor>(ctorInfo); + var ctor1 = ReflectionUtilities.EmitConstructor>(ctorInfo); Assert.IsInstanceOf(ctor1()); ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, new[] { typeof(int) }, null); - var ctor3 = ReflectionUtilities.EmitCtor>(ctorInfo); + var ctor3 = ReflectionUtilities.EmitConstructor>(ctorInfo); Assert.IsInstanceOf(ctor3(42)); - Assert.Throws(() => ReflectionUtilities.EmitCtor>(ctorInfo)); + Assert.Throws(() => ReflectionUtilities.EmitConstructor>(ctorInfo)); } [Test] public void EmitCtorEmitsPrivateCtor() { - var ctor = ReflectionUtilities.EmitCtor>(); + var ctor = ReflectionUtilities.EmitConstuctor>(); Assert.IsInstanceOf(ctor("foo")); } [Test] public void EmitCtorThrowsIfNotFound() { - Assert.Throws(() => ReflectionUtilities.EmitCtor>()); + Assert.Throws(() => ReflectionUtilities.EmitConstuctor>()); } [Test] public void EmitCtorThrowsIfInvalid() { var ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); - Assert.Throws(() => ReflectionUtilities.EmitCtor>(ctorInfo)); + Assert.Throws(() => ReflectionUtilities.EmitConstructor>(ctorInfo)); } [Test] public void EmitCtorReturnsNull() { - Assert.IsNull(ReflectionUtilities.EmitCtor>(false)); + Assert.IsNull(ReflectionUtilities.EmitConstuctor>(false)); } [Test] From 3d52f2676ed1cab1602a6f8129187162d0e3f920 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 10:59:04 +0200 Subject: [PATCH 111/585] Add possibility to extend the umb-box-header + add rollback button --- .../content/umbcontentnodeinfo.directive.js | 4 ++++ .../src/less/components/umb-box.less | 3 +++ .../content/umb-content-node-info.html | 12 +++++++++++- .../html/umb-box/umb-box-header.html | 19 +++++++++++-------- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index b2e64983d6..d274cb495c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -101,6 +101,10 @@ scope.node.template = templateAlias; }; + scope.openRollback = function() { + console.log("rollback"); + }; + function loadAuditTrail() { scope.loadingAuditTrail = true; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less index f2cacc26b3..fb83504a1f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less @@ -8,6 +8,9 @@ .umb-box-header { padding: 10px 20px; border-bottom: 1px solid @gray-9; + display: flex; + align-items: center; + justify-content: space-between; } .umb-box-header-title { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 03d4318ba2..9849ba0c08 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -41,7 +41,17 @@ - + + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html index 8c59061788..e9f771f09c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html @@ -1,10 +1,13 @@ -
-
- - {{title}} -
-
- - {{description}} +
+
+
+ + {{title}} +
+
+ + {{description}} +
+
\ No newline at end of file From eceec9ea3e431fc02e6a33e8b3470361b1c2dd33 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 11:43:14 +0200 Subject: [PATCH 112/585] Set rollback button to be xs + fix padding on xs buttons --- .../src/less/components/buttons/umb-button.less | 2 +- .../src/views/components/content/umb-content-node-info.html | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less index 4b670ab781..d3f2fee5d5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -104,7 +104,7 @@ } .umb-button--xs { - padding: 5px 16px; + padding: 5px 13px; font-size: 14px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 9849ba0c08..77a7161762 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -48,7 +48,8 @@ type="button" button-style="outline" action="openRollback()" - label-key="actions_rollback"> + label-key="actions_rollback" + size="xs"> From 982fb46e7dd38a75c981c39cf747d9d7e00f51bd Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 16 Oct 2018 11:16:45 +0100 Subject: [PATCH 113/585] Fix failing unit test - number of trees gone down as we have removed the user tree (as this is now a fullscreen/wide section/app only) --- src/Umbraco.Tests/Composing/TypeFinderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index a8624e8871..955f6f94c8 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -90,7 +90,7 @@ namespace Umbraco.Tests.Composing Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] typesFound = TypeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); - Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] + Assert.AreEqual(21, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } private static ProfilingLogger GetTestProfilingLogger() From 1c1057412ecad3fd9472fc16b3cadd0cf4a1b07f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 12:40:40 +0200 Subject: [PATCH 114/585] move current rollback version to infinite editor --- .../content/umbcontentnodeinfo.directive.js | 10 ++ .../src/common/services/editor.service.js | 20 +++ .../rollback/rollback.controller.js | 129 ++++++++++++++++++ .../infiniteeditors/rollback/rollback.html | 92 +++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index d274cb495c..416774d6b4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -103,6 +103,16 @@ scope.openRollback = function() { console.log("rollback"); + var rollback = { + node: scope.node, + submit: function(model) { + editorService.close(); + }, + close: function() { + editorService.close(); + } + }; + editorService.rollback(rollback); }; function loadAuditTrail() { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index eab167c2ec..7d1ef3e9b9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -180,6 +180,25 @@ open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#rollback + * @methodOf umbraco.services.editorService + * + * @description + * Opens a rollback editor in infinite editing. + * @param {String} editor.node The node to rollback + * @param {Callback} editor.submit Saves, submits, and closes the editor + * @param {Callback} editor.close Closes the editor + * @returns {Object} editor object + */ + + function rollback(editor) { + editor.view = "views/common/infiniteeditors/rollback/rollback.html"; + editor.size = "small"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#linkPicker @@ -481,6 +500,7 @@ copy: copy, move: move, embed: embed, + rollback: rollback, linkPicker: linkPicker, mediaPicker: mediaPicker, iconPicker: iconPicker, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js new file mode 100644 index 0000000000..8ed6f68df6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -0,0 +1,129 @@ +(function () { + "use strict"; + + function RollbackController($scope, localizationService, assetsService) { + + var vm = this; + + vm.rollback = rollback; + vm.loadVersion = loadVersion; + vm.submit = submit; + vm.close = close; + + ////////// + + function onInit() { + + vm.loading = true; + vm.variantVersions = []; + vm.diff = null; + + // set default title + if(!$scope.model.title) { + localizationService.localize("actions_rollback").then(function(value){ + $scope.model.title = value; + }); + } + + // Load in diff library + assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { + + vm.currentVersion = { + "id": 1, + "createDate": "22/08/2018 13.32", + "name": "Forside", + "properties": [ + { + "alias": "headline", + "label": "Headline", + "value": "Velkommen" + }, + { + "alias": "text", + "label": "Text", + "value": "This is my danish Content" + } + ] + }; + + vm.previousVersions = [ + { + "id": 2, + "name": "Forside", + "createDate": "21/08/2018 19.25" + } + ]; + + vm.loading = false; + + }); + + } + + function rollback() { + console.log("rollback"); + } + + /** + * This will load in a new version + */ + function loadVersion(id) { + + // fake load version + + vm.diff = {}; + vm.diff.properties = []; + + var oldVersion = { + "id": 2, + "name": "Foride", + "properties": [ + { + "alias": "headline", + "label": "Headline", + "value": "" + }, + { + "alias": "text", + "label": "Text", + "value": "This is my danish Content Test" + } + ] + }; + + // find diff in name + vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, oldVersion.name); + + // find diff in properties + angular.forEach(vm.currentVersion.properties, + function(newProperty, index){ + var oldProperty = oldVersion.properties[index]; + var diffProperty = { + "alias": newProperty.alias, + "label": newProperty.label, + "diff": JsDiff.diffWords(newProperty.value, oldProperty.value) + }; + vm.diff.properties.push(diffProperty); + }); + + } + + function submit(model) { + if($scope.model.submit) { + $scope.model.submit(model); + } + } + + function close() { + if($scope.model.close) { + $scope.model.close(); + } + } + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.RollbackController", RollbackController); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html new file mode 100644 index 0000000000..a09f91632a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -0,0 +1,92 @@ +
+ + + + + + + + + + + + + + +
+ +
+

{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})

+ +
+ +
+ +
+ +
Changes
+ + + + + + + + + + + + + +
Name + + {{part.value}} + {{part.value}} + {{part.value}} + +
{{property.label}} + + {{part.value}} + {{part.value}} + {{part.value}} + +
+ +
+ +
+
+
+ + + + + + + + + + +
+ +
\ No newline at end of file From c63c114563ea6d07a5529a219c62897ac502459e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 16 Oct 2018 12:58:21 +0100 Subject: [PATCH 115/585] Fixes the colors in the icon picker Checked that the color picker property editor is also OK/happy as well that re-uses the directive/component --- .../infiniteeditors/iconpicker/iconpicker.html | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 8a7358116a..c0ca65de9c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -1,6 +1,6 @@ -
@@ -30,17 +30,18 @@ no-dirty-check />
- +
-
- + - +
  • @@ -50,7 +51,7 @@
- + From 203ea8019b6d40cb2beeb38553ab8e28414b01c9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 14:09:57 +0200 Subject: [PATCH 116/585] add language selector --- .../infiniteeditors/rollback/rollback.controller.js | 11 +++++++++++ .../common/infiniteeditors/rollback/rollback.html | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 8ed6f68df6..dc42568d54 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -18,6 +18,17 @@ vm.variantVersions = []; vm.diff = null; + // preselect the active language + if($scope.model.node.variants.length > 1) { + var active = _.find($scope.model.node.variants, function (v) { + return v.active; + }); + + if(active) { + vm.selectedVariant = active; + } + } + // set default title if(!$scope.model.title) { localizationService.localize("actions_rollback").then(function(value){ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index a09f91632a..ef474551df 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -18,6 +18,17 @@ + +
+
+ +
From 16282e10074a9761a288791d105b092dcae35c30 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 14:37:00 +0200 Subject: [PATCH 117/585] remove old client side files for rollback --- .../content/content.rollback.controller.js | 117 ------------------ .../src/views/content/rollback.html | 80 ------------ 2 files changed, 197 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js delete mode 100644 src/Umbraco.Web.UI.Client/src/views/content/rollback.html diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js deleted file mode 100644 index 9da43b1430..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js +++ /dev/null @@ -1,117 +0,0 @@ -(function () { - "use strict"; - - function ContentRollbackController($scope, assetsService) { - - var vm = this; - - vm.rollback = rollback; - vm.loadVersion = loadVersion; - vm.closeDialog = closeDialog; - - function onInit() { - - vm.loading = true; - vm.variantVersions = []; - vm.diff = null; - - // Load in diff library - assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { - - var currentLanguage = $scope.currentNode.metaData.culture; - - vm.currentVersion = { - "id": 1, - "createDate": "22/08/2018 13.32", - "name": "Forside", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "Velkommen" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content" - } - ] - }; - - vm.previousVersions = [ - { - "id": 2, - "name": "Forside", - "createDate": "21/08/2018 19.25" - } - ]; - - vm.loading = false; - - }); - - } - - function rollback() { - console.log("rollback"); - } - - /** - * This will load in a new version - */ - function loadVersion(id) { - - // fake load version - var currentLanguage = $scope.currentNode.metaData.culture; - - vm.diff = {}; - vm.diff.properties = []; - - var oldVersion = { - "id": 2, - "name": "Foride", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content Test" - } - ] - }; - - // find diff in name - vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, oldVersion.name); - - // find diff in properties - angular.forEach(vm.currentVersion.properties, - function(newProperty, index){ - var oldProperty = oldVersion.properties[index]; - var diffProperty = { - "alias": newProperty.alias, - "label": newProperty.label, - "diff": JsDiff.diffWords(newProperty.value, oldProperty.value) - }; - vm.diff.properties.push(diffProperty); - }); - - } - - /** - * This will close the dialog - */ - function closeDialog() { - $scope.nav.hideDialog(); - } - - onInit(); - - } - - angular.module("umbraco").controller("Umbraco.Editors.Content.RollbackController", ContentRollbackController); - -})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/content/rollback.html b/src/Umbraco.Web.UI.Client/src/views/content/rollback.html deleted file mode 100644 index 389b8f30db..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/content/rollback.html +++ /dev/null @@ -1,80 +0,0 @@ -
- -
- - - -
- -
-
Rollback {{ currentNode.name }}
- -
-

{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})

- -
- -
- -
- -
Changes
- - - - - - - - - - - - - -
Name - - {{part.value}} - {{part.value}} - {{part.value}} - -
{{property.label}} - - {{part.value}} - {{part.value}} - {{part.value}} - -
- -
- -
-
- - - -
\ No newline at end of file From 6346710b97200130c80536cadf615fd9e019e902 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 16 Oct 2018 15:05:17 +0200 Subject: [PATCH 118/585] Fix tests --- .../ContentApps/ContentAppDefinitionCollection.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs index bdd7455386..559c3e3fb6 100644 --- a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs +++ b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs @@ -19,10 +19,19 @@ namespace Umbraco.Web.ContentApps _logger = logger; } + private IEnumerable GetCurrentUserGroups() + { + var umbracoContext = Composing.Current.UmbracoContext; + var currentUser = umbracoContext?.Security?.CurrentUser; + return currentUser == null + ? Enumerable.Empty() + : currentUser.Groups; + + } + public IEnumerable GetContentAppsFor(object o, IEnumerable userGroups=null) { - var currentUser = UmbracoContext.Current.Security.CurrentUser; - var roles = currentUser.Groups; + var roles = GetCurrentUserGroups(); var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); var aliases = new HashSet(); From fd6f9fc83b2a25cdd6d76d45ca7fd52724f32687 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 15:11:47 +0200 Subject: [PATCH 119/585] remove rollback context menu item --- src/Umbraco.Web/Trees/ContentTreeController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 3a990a7741..436c65d95d 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -238,7 +238,6 @@ namespace Umbraco.Web.Trees AddActionNode(item, menu, true); - AddActionNode(item, menu); AddActionNode(item, menu, convert: true); AddActionNode(item, menu); AddActionNode(item, menu, convert: true); From 69640515cff0a4d70f36b087ed8544af2351248a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 17 Oct 2018 13:44:24 +1100 Subject: [PATCH 120/585] fixes issue with sorting on published --- .../Repositories/Implement/DocumentRepository.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 016e7d4f02..4ab4b556b4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -141,7 +141,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql .LeftJoin(nested => nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") - .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); + .On((version, ccv) => version.Id == ccv.VersionId, "pcv", "ccv"); sql .Where(x => x.NodeObjectType == NodeObjectTypeId); @@ -882,10 +882,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // variant: left join may yield NULL or something, and that determines published var joins = Sql() - .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype") - .LeftJoin(nested => - nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") - .On((pcv, ccv) => pcv.Id == ccv.VersionId, "pcv", "ccv"); // join on *published* content version + .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype"); sql = InsertJoins(sql, joins); From ba186144a01b442136c4a747fd5e84888d3a124e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 17 Oct 2018 18:09:52 +1100 Subject: [PATCH 121/585] Updates models to allow dirty tracking on variants --- .../Collections/CompositeIntStringKey.cs | 3 +- .../Collections/IReadOnlyKeyedCollection.cs | 16 +++ src/Umbraco.Core/ContentExtensions.cs | 2 +- src/Umbraco.Core/Models/Content.cs | 23 +-- src/Umbraco.Core/Models/ContentBase.cs | 49 +++++-- src/Umbraco.Core/Models/CultureName.cs | 82 +++++++++++ .../Models/CultureNameCollection.cs | 136 ++++++++++++++++++ src/Umbraco.Core/Models/IContent.cs | 3 +- src/Umbraco.Core/Models/IContentBase.cs | 3 +- .../Models/PropertyGroupCollection.cs | 3 +- .../Implement/DocumentRepository.cs | 6 +- src/Umbraco.Core/Umbraco.Core.csproj | 3 + src/Umbraco.Tests/Models/ContentTests.cs | 27 ++++ src/Umbraco.Tests/Models/VariationTests.cs | 8 +- .../Repositories/DocumentRepositoryTest.cs | 2 +- .../Models/Mapping/ContentMapperProfile.cs | 2 +- src/Umbraco.Web/umbraco.presentation/page.cs | 2 +- 17 files changed, 332 insertions(+), 38 deletions(-) create mode 100644 src/Umbraco.Core/Collections/IReadOnlyKeyedCollection.cs create mode 100644 src/Umbraco.Core/Models/CultureName.cs create mode 100644 src/Umbraco.Core/Models/CultureNameCollection.cs diff --git a/src/Umbraco.Core/Collections/CompositeIntStringKey.cs b/src/Umbraco.Core/Collections/CompositeIntStringKey.cs index eb9db80990..74ef4e45e1 100644 --- a/src/Umbraco.Core/Collections/CompositeIntStringKey.cs +++ b/src/Umbraco.Core/Collections/CompositeIntStringKey.cs @@ -2,6 +2,7 @@ namespace Umbraco.Core.Collections { + /// /// Represents a composite key of (int, string) for fast dictionaries. /// @@ -40,4 +41,4 @@ namespace Umbraco.Core.Collections public static bool operator !=(CompositeIntStringKey key1, CompositeIntStringKey key2) => key1._key2 != key2._key2 || key1._key1 != key2._key1; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Collections/IReadOnlyKeyedCollection.cs b/src/Umbraco.Core/Collections/IReadOnlyKeyedCollection.cs new file mode 100644 index 0000000000..8d78241275 --- /dev/null +++ b/src/Umbraco.Core/Collections/IReadOnlyKeyedCollection.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Collections +{ + /// + /// A readonly keyed collection + /// + /// + public interface IReadOnlyKeyedCollection : IReadOnlyList + { + IEnumerable Keys { get; } + bool TryGetValue(TKey key, out TVal val); + TVal this[string key] { get; } + bool Contains(TKey key); + } +} diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index e36731a8cb..4f88c2b803 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -133,7 +133,7 @@ namespace Umbraco.Core } #endregion - + /// /// Removes characters that are not valide XML characters from all entity properties /// of type string. See: http://stackoverflow.com/a/961504/5018 diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 238d87b186..2ee6762e61 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using Umbraco.Core.Collections; using Umbraco.Core.Exceptions; namespace Umbraco.Core.Models @@ -20,8 +21,8 @@ namespace Umbraco.Core.Models private PublishedState _publishedState; private DateTime? _releaseDate; private DateTime? _expireDate; - private Dictionary _publishInfos; - private Dictionary _publishInfosOrig; + private CultureNameCollection _publishInfos; + private CultureNameCollection _publishInfosOrig; private HashSet _editedCultures; private static readonly Lazy Ps = new Lazy(); @@ -221,13 +222,13 @@ namespace Umbraco.Core.Models public bool IsCulturePublished(string culture) // just check _publishInfos // a non-available culture could not become published anyways - => _publishInfos != null && _publishInfos.ContainsKey(culture); + => _publishInfos != null && _publishInfos.Contains(culture); /// public bool WasCulturePublished(string culture) // just check _publishInfosOrig - a copy of _publishInfos // a non-available culture could not become published anyways - => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); + => _publishInfosOrig != null && _publishInfosOrig.Contains(culture); /// public bool IsCultureEdited(string culture) @@ -237,7 +238,7 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] - public IReadOnlyDictionary PublishNames => _publishInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames; + public IReadOnlyKeyedCollection PublishNames => _publishInfos ?? NoNames; /// public string GetPublishName(string culture) @@ -267,9 +268,11 @@ namespace Umbraco.Core.Models throw new ArgumentNullOrEmptyException(nameof(culture)); if (_publishInfos == null) - _publishInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); + _publishInfos = new CultureNameCollection(); - _publishInfos[culture.ToLowerInvariant()] = (name, date); + //TODO: Track changes? + + _publishInfos.AddOrUpdate(culture, name, date); } private void ClearPublishInfos() @@ -430,7 +433,11 @@ namespace Umbraco.Core.Models // take care of publish infos _publishInfosOrig = _publishInfos == null ? null - : new Dictionary(_publishInfos, StringComparer.OrdinalIgnoreCase); + : new CultureNameCollection(_publishInfos); + + if (_publishInfos != null) + foreach (var cultureName in _publishInfos) + cultureName.ResetDirtyProperties(rememberDirty); } /// diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index bf2fd580d9..2336188c50 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Web; +using Umbraco.Core.Collections; using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Entities; @@ -19,14 +20,14 @@ namespace Umbraco.Core.Models [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] public abstract class ContentBase : TreeEntityBase, IContentBase { - protected static readonly Dictionary NoNames = new Dictionary(); + protected static readonly CultureNameCollection NoNames = new CultureNameCollection(); private static readonly Lazy Ps = new Lazy(); private int _contentTypeId; protected IContentTypeComposition ContentTypeBase; private int _writerId; private PropertyCollection _properties; - private Dictionary _cultureInfos; + private CultureNameCollection _cultureInfos; /// /// Initializes a new instance of the class. @@ -69,7 +70,7 @@ namespace Umbraco.Core.Models public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); - public readonly PropertyInfo NamesSelector = ExpressionHelper.GetPropertyInfo>(x => x.CultureNames); + public readonly PropertyInfo CultureNamesSelector = ExpressionHelper.GetPropertyInfo>(x => x.CultureNames); } protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -147,18 +148,18 @@ namespace Umbraco.Core.Models /// public IEnumerable AvailableCultures - => _cultureInfos?.Select(x => x.Key) ?? Enumerable.Empty(); + => _cultureInfos?.Keys ?? Enumerable.Empty(); /// public bool IsCultureAvailable(string culture) - => _cultureInfos != null && _cultureInfos.ContainsKey(culture); + => _cultureInfos != null && _cultureInfos.Contains(culture); /// [DataMember] - public virtual IReadOnlyDictionary CultureNames => _cultureInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames; - + public virtual IReadOnlyKeyedCollection CultureNames => _cultureInfos ?? NoNames; + /// - public virtual string GetCultureName(string culture) + public string GetCultureName(string culture) { if (culture.IsNullOrWhiteSpace()) return Name; if (!ContentTypeBase.VariesByCulture()) return null; @@ -176,7 +177,7 @@ namespace Umbraco.Core.Models } /// - public virtual void SetCultureName(string name, string culture) + public void SetCultureName(string name, string culture) { if (ContentTypeBase.VariesByCulture()) // set on variant content type { @@ -202,16 +203,18 @@ namespace Umbraco.Core.Models } } + //fixme: this isn't used anywhere internal void TouchCulture(string culture) { if (ContentTypeBase.VariesByCulture() && _cultureInfos != null && _cultureInfos.TryGetValue(culture, out var infos)) - _cultureInfos[culture] = (infos.Name, DateTime.Now); + _cultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now); } protected void ClearCultureInfos() { + if (_cultureInfos != null) + _cultureInfos.Clear(); _cultureInfos = null; - OnPropertyChanged(Ps.Value.NamesSelector); } protected void ClearCultureInfo(string culture) @@ -223,7 +226,6 @@ namespace Umbraco.Core.Models _cultureInfos.Remove(culture); if (_cultureInfos.Count == 0) _cultureInfos = null; - OnPropertyChanged(Ps.Value.NamesSelector); } // internal for repository @@ -236,10 +238,22 @@ namespace Umbraco.Core.Models throw new ArgumentNullOrEmptyException(nameof(culture)); if (_cultureInfos == null) - _cultureInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); + { + _cultureInfos = new CultureNameCollection(); + _cultureInfos.CollectionChanged += CultureNamesCollectionChanged; + } - _cultureInfos[culture.ToLowerInvariant()] = (name, date); - OnPropertyChanged(Ps.Value.NamesSelector); + _cultureInfos.AddOrUpdate(culture, name, date); + } + + /// + /// Event handler for when the culture names collection is modified + /// + /// + /// + private void CultureNamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.CultureNamesSelector); } #endregion @@ -387,6 +401,11 @@ namespace Umbraco.Core.Models // also reset dirty changes made to user's properties foreach (var prop in Properties) prop.ResetDirtyProperties(rememberDirty); + + // take care of culture names + if (_cultureInfos != null) + foreach (var cultureName in _cultureInfos) + cultureName.ResetDirtyProperties(rememberDirty); } /// diff --git a/src/Umbraco.Core/Models/CultureName.cs b/src/Umbraco.Core/Models/CultureName.cs new file mode 100644 index 0000000000..64db50c36d --- /dev/null +++ b/src/Umbraco.Core/Models/CultureName.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// The name of a content variant for a given culture + /// + public class CultureName : BeingDirtyBase, IDeepCloneable, IEquatable + { + private DateTime _date; + private string _name; + private static readonly Lazy Ps = new Lazy(); + + public CultureName(string culture, string name, DateTime date) + { + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("message", nameof(culture)); + Culture = culture; + _name = name; + _date = date; + } + + public string Culture { get; private set; } + + public string Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); + } + + public DateTime Date + { + get => _date; + set => SetPropertyValueAndDetectChanges(value, ref _date, Ps.Value.DateSelector); + } + + public object DeepClone() + { + return new CultureName(Culture, Name, Date); + } + + public override bool Equals(object obj) + { + return obj is CultureName && Equals((CultureName)obj); + } + + public bool Equals(CultureName other) + { + return Culture == other.Culture && + Name == other.Name; + } + + public override int GetHashCode() + { + var hashCode = 479558943; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Culture); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name); + return hashCode; + } + + /// + /// Allows deconstructing into culture and name + /// + /// + /// + public void Deconstruct(out string culture, out string name) + { + culture = Culture; + name = Name; + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo CultureSelector = ExpressionHelper.GetPropertyInfo(x => x.Culture); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo DateSelector = ExpressionHelper.GetPropertyInfo(x => x.Date); + } + } +} diff --git a/src/Umbraco.Core/Models/CultureNameCollection.cs b/src/Umbraco.Core/Models/CultureNameCollection.cs new file mode 100644 index 0000000000..3c00603c88 --- /dev/null +++ b/src/Umbraco.Core/Models/CultureNameCollection.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using Umbraco.Core.Collections; + +namespace Umbraco.Core.Models +{ + + + /// + /// The culture names of a content's variants + /// + public class CultureNameCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable, IReadOnlyKeyedCollection + { + /// + /// Creates a new collection from another collection + /// + /// + public CultureNameCollection(IEnumerable names) : base(StringComparer.InvariantCultureIgnoreCase) + { + foreach (var n in names) + Add(n); + } + + /// + /// Creates a new collection + /// + public CultureNameCollection() : base(StringComparer.InvariantCultureIgnoreCase) + { + } + + /// + /// Returns all keys in the collection + /// + public IEnumerable Keys => Dictionary != null ? Dictionary.Keys : this.Select(x => x.Culture); + + public bool TryGetValue(string culture, out CultureName name) + { + name = this.FirstOrDefault(x => x.Culture.InvariantEquals(culture)); + return name != null; + } + + /// + /// Add or update the + /// + /// + public void AddOrUpdate(string culture, string name, DateTime date) + { + culture = culture.ToLowerInvariant(); + if (TryGetValue(culture, out var found)) + { + found.Name = name; + found.Date = date; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, found, found)); + } + else + Add(new CultureName(culture, name, date)); + } + + /// + /// Gets the index for a specified culture + /// + public int IndexOfKey(string key) + { + for (var i = 0; i < Count; i++) + { + if (this[i].Culture.InvariantEquals(key)) + return i; + } + return -1; + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + CollectionChanged?.Invoke(this, args); + } + + public object DeepClone() + { + var clone = new CultureNameCollection(); + foreach (var name in this) + { + clone.Add((CultureName)name.DeepClone()); + } + return clone; + } + + protected override string GetKeyForItem(CultureName item) + { + return item.Culture; + } + + /// + /// Resets the collection to only contain the instances referenced in the parameter. + /// + /// The property groups. + /// + internal void Reset(IEnumerable names) + { + Clear(); + foreach (var name in names) + Add(name); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + protected override void SetItem(int index, CultureName item) + { + base.SetItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + } + + protected override void RemoveItem(int index) + { + var removed = this[index]; + base.RemoveItem(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } + + protected override void InsertItem(int index, CultureName item) + { + base.InsertItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + + protected override void ClearItems() + { + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + } +} diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index d9bc32aaf0..3ddffe8f75 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Collections; namespace Umbraco.Core.Models { @@ -132,7 +133,7 @@ namespace Umbraco.Core.Models /// Because a dictionary key cannot be null this cannot get the invariant /// name, which must be get via the property. /// - IReadOnlyDictionary PublishNames { get; } + IReadOnlyKeyedCollection PublishNames { get; } /// /// Gets the published cultures. diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 460bd521d4..811cbf74f3 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Collections; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models @@ -57,7 +58,7 @@ namespace Umbraco.Core.Models /// Because a dictionary key cannot be null this cannot contain the invariant /// culture name, which must be get or set via the property. /// - IReadOnlyDictionary CultureNames { get; } + IReadOnlyKeyedCollection CultureNames { get; } /// /// Gets the available cultures. diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index d10b375285..80b663fa05 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -8,6 +8,7 @@ using System.Threading; namespace Umbraco.Core.Models { + /// /// Represents a collection of objects /// @@ -168,7 +169,7 @@ namespace Umbraco.Core.Models var clone = new PropertyGroupCollection(); foreach (var group in this) { - clone.Add((PropertyGroup) group.DeepClone()); + clone.Add((PropertyGroup)group.DeepClone()); } return clone; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index bf41cd1ad1..83d9e80f5f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -1172,8 +1172,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // using the default culture if it has a name, otherwise anything we can var defaultCulture = LanguageRepository.GetDefaultIsoCode(); content.Name = defaultCulture != null && content.CultureNames.TryGetValue(defaultCulture, out var cultureName) - ? cultureName - : content.CultureNames.First().Value; + ? cultureName.Name + : content.CultureNames[0].Name; } else { @@ -1234,7 +1234,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // update the name, and the publish name if published content.SetCultureName(uniqueName, culture); - if (publishing && content.PublishNames.ContainsKey(culture)) + if (publishing && content.PublishNames.Contains(culture)) content.SetPublishInfo(culture, uniqueName, DateTime.Now); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index aef18e59db..0eba543e0d 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -109,6 +109,7 @@ + @@ -374,6 +375,8 @@ + + diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 32fbd37d0e..a7d0f2f050 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -42,6 +42,33 @@ namespace Umbraco.Tests.Models Container.Register(_ => Mock.Of()); } + [Test] + public void Variant_Names_Track_Dirty_Changes() + { + var contentType = new ContentType(-1) { Alias = "contentType" }; + var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; + + const string langFr = "fr-FR"; + + contentType.Variations = ContentVariation.Culture; + + Assert.IsFalse(content.IsPropertyDirty("CultureNames")); //hasn't been changed + + content.SetCultureName("name-fr", langFr); + Assert.IsTrue(content.IsPropertyDirty("CultureNames")); //now it will be changed since the collection has changed + var frCultureName = content.CultureNames[langFr]; + Assert.IsFalse(frCultureName.IsPropertyDirty("Date")); //this won't be dirty because it wasn't actually updated, just created + + content.ResetDirtyProperties(); + + Assert.IsFalse(content.IsPropertyDirty("CultureNames")); //it's been reset + Assert.IsTrue(content.WasPropertyDirty("CultureNames")); + + content.SetCultureName("name-fr", langFr); + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); //this will be dirty because it was already created and now has been updated + Assert.IsTrue(content.IsPropertyDirty("CultureNames")); //it's true now since we've updated a name + } + [Test] public void Get_Non_Grouped_Properties() { diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index 8d566e81f2..0e62c41f46 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -237,10 +237,10 @@ namespace Umbraco.Tests.Models // variant dictionary of names work Assert.AreEqual(2, content.CultureNames.Count); - Assert.IsTrue(content.CultureNames.ContainsKey(langFr)); - Assert.AreEqual("name-fr", content.CultureNames[langFr]); - Assert.IsTrue(content.CultureNames.ContainsKey(langUk)); - Assert.AreEqual("name-uk", content.CultureNames[langUk]); + Assert.IsTrue(content.CultureNames.Contains(langFr)); + Assert.AreEqual("name-fr", content.CultureNames[langFr].Name); + Assert.IsTrue(content.CultureNames.Contains(langUk)); + Assert.AreEqual("name-uk", content.CultureNames[langUk].Name); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 68e29c4efe..62c3116ab1 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -767,7 +767,7 @@ namespace Umbraco.Tests.Persistence.Repositories foreach (var r in result) { var isInvariant = r.ContentType.Alias == "umbInvariantTextpage"; - var name = isInvariant ? r.Name : r.CultureNames["en-US"]; + var name = isInvariant ? r.Name : r.CultureNames["en-US"].Name; var namePrefix = isInvariant ? "INV" : "VAR"; //ensure the correct name (invariant vs variant) is in the result diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 8b23763763..4557dfb1bd 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -132,7 +132,7 @@ namespace Umbraco.Web.Models.Mapping // if we don't have a name for a culture, it means the culture is not available, and // hey we should probably not be mapping it, but it's too late, return a fallback name - return source.CultureNames.TryGetValue(culture, out var name) && !name.IsNullOrWhiteSpace() ? name : $"(({source.Name}))"; + return source.CultureNames.TryGetValue(culture, out var name) && !name.Name.IsNullOrWhiteSpace() ? name.Name : $"(({source.Name}))"; } } } diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index ac35c336b2..91cf690996 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -396,7 +396,7 @@ namespace umbraco return _cultureInfos; return _cultureInfos = _inner.PublishNames - .ToDictionary(x => x.Key, x => new PublishedCultureInfo(x.Key, x.Value, _inner.GetPublishDate(x.Key) ?? DateTime.MinValue)); + .ToDictionary(x => x.Culture, x => new PublishedCultureInfo(x.Culture, x.Name, x.Date)); } } From 5c796b15ade151e018e93e20b7cc2c1914099e0e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 11:01:33 +0200 Subject: [PATCH 122/585] Remove console.log --- .../components/content/umbcontentnodeinfo.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 416774d6b4..327d99a8c6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -102,7 +102,7 @@ }; scope.openRollback = function() { - console.log("rollback"); + var rollback = { node: scope.node, submit: function(model) { From c05495f3587d29f41c33728284cf58d8708560d5 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 12:15:57 +0100 Subject: [PATCH 123/585] WIP: Adds a new API endpoint in the editor/webapi for ContenController to list rollback versions available for a culture --- src/Umbraco.Web/Editors/ContentController.cs | 56 +++++++++++++++++++ .../Models/ContentEditing/RollbackVersion.cs | 13 +++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 3 files changed, 70 insertions(+) create mode 100644 src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 1fcee6d727..d35a801766 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1702,5 +1702,61 @@ namespace Umbraco.Web.Editors Services.NotificationService.SetNotifications(Security.CurrentUser, content, notifyOptions); } + [HttpGet] + public IEnumerable GetRollbackVersions(int contentId, string cultureName) + { + var rollbackVersions = new List(); + + //Return a list of all versions of a specific content node + var versions = Services.ContentService.GetVersions(contentId); + + //Not all nodes are variants & thus culture can be null + //Only filter the collection + if(cultureName != null) + { + versions = versions.Where(x => x.PublishDate == x.GetPublishDate(cultureName)); + } + + foreach(var version in versions) + { + var rollbackVersion = new RollbackVersion(); + + //Version ID + rollbackVersion.VersionId = version.VersionId; + + //Date of version + var cultureDate = version.GetPublishDate(cultureName); + if (cultureDate.HasValue) + { + rollbackVersion.VersionDate = cultureDate.Value; + } + else if (version.PublishDate.HasValue) + { + rollbackVersion.VersionDate = version.PublishDate.Value; + } + + //Name of publisher + //TODO: Reviewer would this extra info be expensive? + var publisherId = version.PublisherId; + + if (publisherId.HasValue) + { + var publisher = Services.UserService.GetUserById(version.PublisherId.Value); + rollbackVersion.VersionAuthorName = publisher.Name; + } + + rollbackVersions.Add(rollbackVersion); + } + + return rollbackVersions; + } + + //[EnsureUserPermissionForContent("id", 'D')] + [HttpPost] + public void PostRollback() + { + + } + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs new file mode 100644 index 0000000000..d496a89d35 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs @@ -0,0 +1,13 @@ +using System; + +namespace Umbraco.Web.Models.ContentEditing +{ + public class RollbackVersion + { + public int VersionId { get; set; } + + public DateTime VersionDate { get; set; } + + public string VersionAuthorName { get; set; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index faf08849ce..5235728814 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -147,6 +147,7 @@ + From 7c1c6f68063fa42c95f8949a77f1f456ac963d73 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 13:57:35 +0200 Subject: [PATCH 124/585] Fix AlterTableBuilder, was ignoring primary keys --- .../Expressions/AlterColumnExpression.cs | 1 - .../Alter/Table/AlterTableBuilder.cs | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs index f19fc94c97..1b00b03ca2 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs @@ -18,7 +18,6 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Expressions protected override string GetSql() { - return string.Format(SqlSyntax.AlterColumn, SqlSyntax.GetQuotedTableName(TableName), SqlSyntax.Format(Column)); diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs index 989ce95002..8d95d9c14c 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs @@ -2,6 +2,8 @@ using NPoco; using Umbraco.Core.Migrations.Expressions.Alter.Expressions; using Umbraco.Core.Migrations.Expressions.Common.Expressions; +using Umbraco.Core.Migrations.Expressions.Create.Expressions; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Migrations.Expressions.Alter.Table @@ -87,6 +89,21 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Table public IAlterTableColumnOptionBuilder PrimaryKey() { CurrentColumn.IsPrimaryKey = true; + + // see notes in CreateTableBuilder + if (Expression.DatabaseType.IsMySql() == false) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey) + { + Constraint = + { + TableName = Expression.TableName, + Columns = new[] { CurrentColumn.Name } + } + }; + Expression.Expressions.Add(expression); + } + return this; } @@ -94,6 +111,22 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Table { CurrentColumn.IsPrimaryKey = true; CurrentColumn.PrimaryKeyName = primaryKeyName; + + // see notes in CreateTableBuilder + if (Expression.DatabaseType.IsMySql() == false) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey) + { + Constraint = + { + ConstraintName = primaryKeyName, + TableName = Expression.TableName, + Columns = new[] { CurrentColumn.Name } + } + }; + Expression.Expressions.Add(expression); + } + return this; } From d9864eb6d6beda445b34c50a9927bab0a5560674 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 13:19:09 +0100 Subject: [PATCH 125/585] * Make the culturename nullable for invariant content * fallback to update date and writer id if its not published --- src/Umbraco.Web/Editors/ContentController.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index d35a801766..0fe017925b 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1703,7 +1703,7 @@ namespace Umbraco.Web.Editors } [HttpGet] - public IEnumerable GetRollbackVersions(int contentId, string cultureName) + public IEnumerable GetRollbackVersions(int contentId, string cultureName = null) { var rollbackVersions = new List(); @@ -1730,20 +1730,17 @@ namespace Umbraco.Web.Editors { rollbackVersion.VersionDate = cultureDate.Value; } - else if (version.PublishDate.HasValue) + else { - rollbackVersion.VersionDate = version.PublishDate.Value; + rollbackVersion.VersionDate = version.UpdateDate; } //Name of publisher //TODO: Reviewer would this extra info be expensive? var publisherId = version.PublisherId; - - if (publisherId.HasValue) - { - var publisher = Services.UserService.GetUserById(version.PublisherId.Value); - rollbackVersion.VersionAuthorName = publisher.Name; - } + var userId = version.PublisherId.HasValue ? version.PublisherId.Value : version.WriterId; + var publisher = Services.UserService.GetUserById(userId); + rollbackVersion.VersionAuthorName = publisher.Name; rollbackVersions.Add(rollbackVersion); } From 27ce1f34bb43020526b5a07a75967ebd509610ee Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 13:24:23 +0100 Subject: [PATCH 126/585] Make the JSON model over the API control be the correct casing & make Mads happy :) --- src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs index d496a89d35..88df12883d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs +++ b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs @@ -1,13 +1,18 @@ using System; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { + [DataContract(Name = "rollbackVersion", Namespace = "")] public class RollbackVersion { + [DataMember(Name = "versionId")] public int VersionId { get; set; } + [DataMember(Name = "versionDate")] public DateTime VersionDate { get; set; } + [DataMember(Name = "versionAuthorName")] public string VersionAuthorName { get; set; } } } From 2be227358961e53584441d8d3f0303ca8f44938b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 17 Oct 2018 14:33:42 +0200 Subject: [PATCH 127/585] wire up versions end point --- .../src/common/resources/content.resource.js | 32 +++++++++++++++++++ .../rollback/rollback.controller.js | 17 +++++----- .../infiniteeditors/rollback/rollback.html | 2 +- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 721cd4da57..8de368be1a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -808,6 +808,38 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to create blueprint from content with id " + contentId ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersions + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns an array of previous version id's, given a node id and a culture + * + * ##usage + *
+          * contentResource.getRollbackVersions(id, culture)
+          *    .then(function(versions) {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id Id of node + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object containing the url. + * + */ + getRollbackVersions: function (contentId, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersions", { + contentId: contentId, cultureName: culture + }) + ), + "Failed to get rollback versions for content item with id " + contentId + ); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index dc42568d54..ec5b3f2242 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function RollbackController($scope, localizationService, assetsService) { + function RollbackController($scope, contentResource, localizationService, assetsService) { var vm = this; @@ -57,13 +57,14 @@ ] }; - vm.previousVersions = [ - { - "id": 2, - "name": "Forside", - "createDate": "21/08/2018 19.25" - } - ]; + const nodeId = $scope.model.node.id; + const culture = vm.selectedVariant ? vm.selectedVariant.language.culture : null; + + contentResource.getRollbackVersions(nodeId, culture) + .then(function(data){ + console.log(data); + vm.previousVersions = data; + }); vm.loading = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index ef474551df..a4f571f832 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -39,7 +39,7 @@ From d324466fe1c57166f2e1310de109df095a9d8ff7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 13:58:18 +0200 Subject: [PATCH 128/585] Fix LockDto, had wrong primary key + missing rows --- src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs | 1 + src/Umbraco.Core/Persistence/Dtos/LockDto.cs | 4 ++-- src/Umbraco.Tests/Persistence/LocksTests.cs | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index ba491fb5e1..cf00e79800 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -149,6 +149,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.KeyValues, Name = "KeyValues" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" }); } diff --git a/src/Umbraco.Core/Persistence/Dtos/LockDto.cs b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs index 833d262e26..b5878141f3 100644 --- a/src/Umbraco.Core/Persistence/Dtos/LockDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs @@ -4,12 +4,12 @@ using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Persistence.Dtos { [TableName(Constants.DatabaseSchema.Tables.Lock)] - [PrimaryKey("id")] + [PrimaryKey("id", AutoIncrement = false)] [ExplicitColumns] internal class LockDto { [Column("id")] - [PrimaryKeyColumn(Name = "PK_umbracoLock")] + [PrimaryKeyColumn(Name = "PK_umbracoLock", AutoIncrement = false)] public int Id { get; set; } [Column("value")] diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs index 4dfb90c8fd..819dbc89ed 100644 --- a/src/Umbraco.Tests/Persistence/LocksTests.cs +++ b/src/Umbraco.Tests/Persistence/LocksTests.cs @@ -25,12 +25,9 @@ namespace Umbraco.Tests.Persistence using (var scope = ScopeProvider.CreateScope()) { var database = scope.Database; - database.Execute("SET IDENTITY_INSERT umbracoLock ON"); database.Insert("umbracoLock", "id", false, new LockDto { Id = 1, Name = "Lock.1" }); database.Insert("umbracoLock", "id", false, new LockDto { Id = 2, Name = "Lock.2" }); database.Insert("umbracoLock", "id", false, new LockDto { Id = 3, Name = "Lock.3" }); - database.Execute("SET IDENTITY_INSERT umbracoLock OFF"); - database.CompleteTransaction(); scope.Complete(); } } From 71cec84f78818cd25003bd5533e7bb1bdf53e977 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 13:58:42 +0200 Subject: [PATCH 129/585] Fix MigrationExpressionBase, minor typo --- src/Umbraco.Core/Migrations/MigrationExpressionBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs index f1c535b466..4c5a1f1aa7 100644 --- a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs @@ -55,7 +55,7 @@ namespace Umbraco.Core.Migrations if (string.IsNullOrWhiteSpace(sql)) { - Logger.Info(GetType(), "SQL [{ContextIndex}: ", Context.Index); + Logger.Info(GetType(), "SQL [{ContextIndex}]: ", Context.Index); } else { From e80ea9830cc5ec0668cc357d2f8010551ed1d837 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 15:09:00 +0200 Subject: [PATCH 130/585] Fix bad LockDto primary key w/migration --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs | 23 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 25 insertions(+) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index eeaf7533a9..5b0838573e 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -137,6 +137,7 @@ namespace Umbraco.Core.Migrations.Upgrade // resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}... Chain("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}"); + Chain("{77874C77-93E5-4488-A404-A630907CEEF0}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs new file mode 100644 index 0000000000..fbb233927b --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs @@ -0,0 +1,23 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class FixLockTablePrimaryKey : MigrationBase + { + public FixLockTablePrimaryKey(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // at some point, the KeyValueService dropped the PK and failed to re-create it, + // so the PK is gone - make sure we have one, and create if needed + + var constraints = SqlSyntax.GetConstraintsPerTable(Database); + var exists = constraints.Any(x => x.Item2 == "PK_umbracoLock"); + + if (!exists) + Create.PrimaryKey("PK_umbracoLock").OnTable(Constants.DatabaseSchema.Tables.Lock).Column("id").Do(); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index aef18e59db..6951a9e17a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -357,6 +357,7 @@ + From 581658086f7967da0d5768826b87d2ce4403e066 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 15:09:59 +0200 Subject: [PATCH 131/585] Fix KeyValueService for proper initialization --- src/Umbraco.Core/Runtime/CoreRuntime.cs | 31 +++----- .../Services/Implement/KeyValueService.cs | 79 ++++++++++++++----- 2 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 8f3b8991e4..0d967cb054 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; +using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Runtime { @@ -313,21 +314,19 @@ namespace Umbraco.Core.Runtime // else // look for a matching migration entry - bypassing services entirely - they are not 'up' yet // fixme - in a LB scenario, ensure that the DB gets upgraded only once! - bool exists; + bool noUpgrade; try { - exists = EnsureUmbracoUpgradeState(databaseFactory, logger); + noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); } catch (Exception e) { - // can connect to the database but cannot access the migration table... need to install + // can connect to the database but cannot check the upgrade state... oops logger.Warn(e, "Could not check the upgrade state."); - logger.Debug("Could not check the upgrade state, need to install Umbraco."); - _state.Level = RuntimeLevel.Install; - return; + throw new BootFailedException("Could not check the upgrade state.", e); } - if (exists) + if (noUpgrade) { // the database version matches the code & files version, all clear, can run _state.Level = RuntimeLevel.Run; @@ -345,27 +344,19 @@ namespace Umbraco.Core.Runtime protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) { - // no scope, no key value service - just directly accessing the database - var umbracoPlan = new UmbracoPlan(); var stateValueKey = Upgrader.GetStateValueKey(umbracoPlan); - string state; + // no scope, no service - just directly accessing the database using (var database = databaseFactory.CreateDatabase()) { - var sql = databaseFactory.SqlContext.Sql() - .Select() - .From() - .Where(x => x.Key == stateValueKey); - state = database.FirstOrDefault(sql)?.Value; + _state.CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); + _state.FinalMigrationState = umbracoPlan.FinalState; } - _state.CurrentMigrationState = state; - _state.FinalMigrationState = umbracoPlan.FinalState; + logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, _state.CurrentMigrationState ?? ""); - logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, state ?? ""); - - return state == _state.FinalMigrationState; + return _state.CurrentMigrationState == _state.FinalMigrationState; } #region Locals diff --git a/src/Umbraco.Core/Services/Implement/KeyValueService.cs b/src/Umbraco.Core/Services/Implement/KeyValueService.cs index cb1c423535..e0d78b0258 100644 --- a/src/Umbraco.Core/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Core/Services/Implement/KeyValueService.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Migrations; -using Umbraco.Core.Migrations.Expressions.Create; using Umbraco.Core.Scoping; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -35,23 +34,23 @@ namespace Umbraco.Core.Services.Implement private void Initialize() { - // all this cannot be achieved via an UmbracoPlan migration since it needs to - // run before any migration, in order to figure out the current plan's state. - // (does not prevent us from using a migration to do it, though) + // the key/value service is entirely self-managed, because it is used by the + // upgrader and anything we might change need to happen before everything else + + // if already running 8, either following an upgrade or an install, + // then everything should be ok (the table should exist, etc) + + if (UmbracoVersion.Local.Major >= 8) + return; + + // else we are upgrading from 7, we can assume that the locks table + // exists, but we need to create everything for key/value using (var scope = _scopeProvider.CreateScope()) { - // assume that if the lock object for key/value exists, then everything is ok - if (scope.Database.Exists(Constants.Locks.KeyValues)) - { - scope.Complete(); - return; - } - var context = new MigrationContext(scope.Database, _logger); var initMigration = new InitializeMigration(context); initMigration.Migrate(); - scope.Complete(); } } @@ -59,7 +58,7 @@ namespace Umbraco.Core.Services.Implement /// /// A custom migration that executes standalone during the Initialize phase of this service. /// - private class InitializeMigration : MigrationBase + internal class InitializeMigration : MigrationBase { public InitializeMigration(IMigrationContext context) : base(context) @@ -67,26 +66,47 @@ namespace Umbraco.Core.Services.Implement public override void Migrate() { + // as long as we are still running 7 this migration will be invoked, + // but due to multiple restarts during upgrades, maybe the table + // exists already + if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) + return; + + Logger.Info("Creating KeyValue structure."); + + // the locks table was initially created with an identity (auto-increment) primary key, + // but we don't want this, especially as we are about to insert a new row into the table, + // so here we drop that identity + DropLockTableIdentity(); + + // insert the lock object for key/value + Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do(); + + // create the key-value table + Create.Table().Do(); + } + + private void DropLockTableIdentity() + { + // one cannot simply drop an identity, that requires a bit of work + // create a temp. id column and copy values Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); Execute.Sql("update umbracoLock set nid = id").Do(); + // drop the id column entirely (cannot just drop identity) Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + // recreate the id column without identity and copy values Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); Execute.Sql("update umbracoLock set id = nid").Do(); + // drop the temp. id column Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + // complete the primary key Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); - - // insert the key-value lock - Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do(); - - // create the key-value table if it's not there - if (TableExists(Constants.DatabaseSchema.Tables.KeyValue) == false) - Create.Table().Do(); } } @@ -169,5 +189,22 @@ namespace Umbraco.Core.Services.Implement return true; } + + /// + /// Gets a value directly from the database, no scope, nothing. + /// + /// Used by to determine the runtime state. + internal static string GetValue(IUmbracoDatabase database, string key) + { + // not 8 yet = no key/value table, no value + if (UmbracoVersion.Local.Major < 8) + return null; + + var sql = database.SqlContext.Sql() + .Select() + .From() + .Where(x => x.Key == key); + return database.FirstOrDefault(sql)?.Value; + } } } From 214d6a9834bed546194101f1490691f082ffe7ba Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 17 Oct 2018 15:17:43 +0200 Subject: [PATCH 132/585] show correct title and date for current version + update versions when language is changed --- .../rollback/rollback.controller.js | 66 ++++++++++--------- .../infiniteeditors/rollback/rollback.html | 6 +- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index ec5b3f2242..cbbc4dd182 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -7,6 +7,8 @@ vm.rollback = rollback; vm.loadVersion = loadVersion; + vm.changeLanguage = changeLanguage; + vm.changeVersion = changeVersion; vm.submit = submit; vm.close = close; @@ -17,15 +19,23 @@ vm.loading = true; vm.variantVersions = []; vm.diff = null; + vm.currentVersion = null; - // preselect the active language + // find the current version for invariant nodes + if($scope.model.node.variants.length === 1) { + vm.currentVersion = $scope.model.node.variants[0]; + } + + // find the current version for nodes with variants if($scope.model.node.variants.length > 1) { var active = _.find($scope.model.node.variants, function (v) { return v.active; }); + // preselect the language in the dropdown if(active) { - vm.selectedVariant = active; + vm.selectedLanguage = active; + vm.currentVersion = active; } } @@ -39,39 +49,35 @@ // Load in diff library assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { - vm.currentVersion = { - "id": 1, - "createDate": "22/08/2018 13.32", - "name": "Forside", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "Velkommen" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content" - } - ] - }; - - const nodeId = $scope.model.node.id; - const culture = vm.selectedVariant ? vm.selectedVariant.language.culture : null; - - contentResource.getRollbackVersions(nodeId, culture) - .then(function(data){ - console.log(data); - vm.previousVersions = data; - }); - - vm.loading = false; + getVersions().then(function(){ + vm.loading = false; + }); }); } + function changeLanguage(language) { + vm.currentVersion = language; + getVersions(); + } + + function changeVersion(version) { + console.log("version", version); + } + + function getVersions() { + + const nodeId = $scope.model.node.id; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.getRollbackVersions(nodeId, culture) + .then(function(data){ + console.log("new", data); + vm.previousVersions = data; + }); + } + function rollback() { console.log("rollback"); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index a4f571f832..6a17cac09b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -23,9 +23,9 @@
@@ -40,7 +40,7 @@ class="input-block-level" ng-model="vm.selectedVersion" ng-options="version.versionDate for version in vm.previousVersions track by version.versionId" - ng-change="vm.loadVersion(vm.selectedVersion.id)"> + ng-change="vm.changeVersion(vm.selectedVersion.id)">
From 2baafd9c9195a05336d47f4b3b91e4a87157a8ef Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 14:39:11 +0100 Subject: [PATCH 133/585] * Renamed API variables to 'culture' to make Stephan happy - updated the AngularJS HTTP Resource call too * Stubbed out some other WIP code - for getting a specific version & attempting to perform a rollback --- .../src/common/resources/content.resource.js | 2 +- src/Umbraco.Web/Editors/ContentController.cs | 48 +++++++++++++++---- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 8de368be1a..6c0cb5e6b2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -835,7 +835,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersions", { - contentId: contentId, cultureName: culture + contentId: contentId, culture: culture }) ), "Failed to get rollback versions for content item with id " + contentId diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 0fe017925b..85b42e59c3 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1703,7 +1703,7 @@ namespace Umbraco.Web.Editors } [HttpGet] - public IEnumerable GetRollbackVersions(int contentId, string cultureName = null) + public IEnumerable GetRollbackVersions(int contentId, string culture = null) { var rollbackVersions = new List(); @@ -1712,10 +1712,10 @@ namespace Umbraco.Web.Editors //Not all nodes are variants & thus culture can be null //Only filter the collection - if(cultureName != null) - { - versions = versions.Where(x => x.PublishDate == x.GetPublishDate(cultureName)); - } + //if(cultureName != null) + //{ + // versions = versions.Where(x => x.PublishDate == x.GetPublishDate(culture)); + //} foreach(var version in versions) { @@ -1725,7 +1725,7 @@ namespace Umbraco.Web.Editors rollbackVersion.VersionId = version.VersionId; //Date of version - var cultureDate = version.GetPublishDate(cultureName); + var cultureDate = version.GetPublishDate(culture); if (cultureDate.HasValue) { rollbackVersion.VersionDate = cultureDate.Value; @@ -1748,12 +1748,40 @@ namespace Umbraco.Web.Editors return rollbackVersions; } - //[EnsureUserPermissionForContent("id", 'D')] - [HttpPost] - public void PostRollback() + [HttpGet] + public ContentItemDisplay GetRollbackVersion(int versionId) { - + var version = Services.ContentService.GetVersion(versionId); + var content = MapToDisplay(version); + return content; } + [HttpPost] + public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*") + { + //TODO: Do we log something - so there is a trail in the logs + //Of who performed the rollback of what document, time + + //Get the current copy of the node + var content = Services.ContentService.GetById(contentId); + + //Get the version + var version = Services.ContentService.GetVersion(versionId); + + //Copy the changes from the version + content.CopyFrom(version); + + //Save & Publish the update + var publishResult = Services.ContentService.SaveAndPublish(content, culture, Security.GetUserId().ResultOr(0)); + if (publishResult.Success == false) + { + var notificationModel = new SimpleNotificationModel(); + AddMessageForPublishStatus(publishResult, notificationModel); + return Request.CreateValidationErrorResponse(notificationModel); + } + + //return ok + return Request.CreateResponse(HttpStatusCode.OK); + } } } From eb8b2698460eaf9766a2aa438093a90c5e725aae Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 17 Oct 2018 16:02:47 +0200 Subject: [PATCH 134/585] wire up getRollbackVersion endpoint --- .../src/common/resources/content.resource.js | 33 +++++++++++++++++++ .../rollback/rollback.controller.js | 9 +++-- .../infiniteeditors/rollback/rollback.html | 2 +- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 6c0cb5e6b2..34a61cca91 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -840,9 +840,42 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to get rollback versions for content item with id " + contentId ); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersion + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a previous version of a content item + * + * ##usage + *
+          * contentResource.getRollbackVersion(versionId)
+          *    .then(function(version) {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} versionId The version Id + * @returns {Promise} resourcePromise object containing the url. + * + */ + getRollbackVersion: function (versionId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersion", { + versionId: versionId + }) + ), + "Failed to get version for content item with id " + versionId + ); } + }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index cbbc4dd182..24fab8f30a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -6,7 +6,6 @@ var vm = this; vm.rollback = rollback; - vm.loadVersion = loadVersion; vm.changeLanguage = changeLanguage; vm.changeVersion = changeVersion; vm.submit = submit; @@ -64,6 +63,12 @@ function changeVersion(version) { console.log("version", version); + contentResource.getRollbackVersion(version.versionId) + .then(function(data){ + console.log(data); + //createDiff(vm.currentVersion, data); + }); + } function getVersions() { @@ -85,7 +90,7 @@ /** * This will load in a new version */ - function loadVersion(id) { + function createDiff(currentVersion, previousVersion) { // fake load version diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 6a17cac09b..70dd189328 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -40,7 +40,7 @@ class="input-block-level" ng-model="vm.selectedVersion" ng-options="version.versionDate for version in vm.previousVersions track by version.versionId" - ng-change="vm.changeVersion(vm.selectedVersion.id)"> + ng-change="vm.changeVersion(vm.selectedVersion)">
From fff7891e533d76d2d1835013e96c46f869cbadd4 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 15:09:29 +0100 Subject: [PATCH 135/585] Filter rollback version to display the VariantDisplay model instead --- src/Umbraco.Web/Editors/ContentController.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 85b42e59c3..73ad3ae81a 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1749,11 +1749,20 @@ namespace Umbraco.Web.Editors } [HttpGet] - public ContentItemDisplay GetRollbackVersion(int versionId) + public ContentVariantDisplay GetRollbackVersion(int versionId, string culture = null) { var version = Services.ContentService.GetVersion(versionId); var content = MapToDisplay(version); - return content; + + + //No culture set - so this is an invariant node - so just list me the first item in here + //TODO: Tripple check invariant nodes still has one item in the collection but with a language of null + if (culture == null) + { + return content.Variants.FirstOrDefault(); + } + + return content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); } [HttpPost] From 15968bc99b26bafd91aeb762311e23982e472013 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 17:27:39 +0200 Subject: [PATCH 136/585] Fix dates in document versions --- src/Umbraco.Core/Models/Content.cs | 28 ++- .../Implement/DocumentRepository.cs | 8 + .../Services/ContentServiceTests.cs | 190 +++++++++++++++++- 3 files changed, 224 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 238d87b186..5d8a4f7222 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -227,7 +227,27 @@ namespace Umbraco.Core.Models public bool WasCulturePublished(string culture) // just check _publishInfosOrig - a copy of _publishInfos // a non-available culture could not become published anyways - => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); + => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); + + // adjust dates to sync between version, cultures etc + // used by the repo when persisting + internal void AdjustDates(DateTime date) + { + foreach (var culture in PublishedCultures.ToList()) + { + if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var publishInfos)) + continue; + + if (_publishInfosOrig != null && _publishInfosOrig.TryGetValue(culture, out var publishInfosOrig) + && publishInfosOrig.Date == publishInfos.Date) + continue; + + _publishInfos[culture] = (publishInfos.Name, date); + + if (CultureNames.TryGetValue(culture, out var name)) + SetCultureInfo(culture, name, date); + } + } /// public bool IsCultureEdited(string culture) @@ -343,6 +363,12 @@ namespace Umbraco.Core.Models SetPublishInfo(c, name, DateTime.Now); } } + else if (culture == null) // invariant culture + { + if (string.IsNullOrWhiteSpace(Name)) + return false; + // PublishName set by repository - nothing to do here + } else // one single culture { var name = GetCultureName(culture); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index bf41cd1ad1..3ceacbfc96 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -341,6 +341,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // persist the variations if (content.ContentType.VariesByCulture()) { + // bump dates to align cultures to version + if (publishing) + content.AdjustDates(contentVersionDto.VersionDate); + // names also impact 'edited' foreach (var (culture, name) in content.CultureNames) if (name != content.GetPublishName(culture)) @@ -499,6 +503,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (content.ContentType.VariesByCulture()) { + // bump dates to align cultures to version + if (publishing) + content.AdjustDates(contentVersionDto.VersionDate); + // names also impact 'edited' foreach (var (culture, name) in content.CultureNames) if (name != content.GetPublishName(culture)) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index f187b8b70d..a82ccd2a0e 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -2111,7 +2111,7 @@ namespace Umbraco.Tests.Services var rollback = contentService.GetById(NodeDto.NodeIdSeed + 4); var rollto = contentService.GetVersion(version1); rollback.CopyFrom(rollto); - rollback.Name = rollto.Name; // must do it explicitely + rollback.Name = rollto.Name; // must do it explicitly contentService.Save(rollback); Assert.IsNotNull(rollback); @@ -2161,6 +2161,194 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Jane Doe", content.GetValue("author")); } + [Test] + public void Can_Rollback_Version_On_Multilingual() + { + var langFr = new Language("fr"); + var langDa = new Language("da"); + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langDa); + + var contentType = MockedContentTypes.CreateSimpleContentType("multi", "Multi"); + contentType.Key = new Guid("45FF9A70-9C5F-448D-A476-DCD23566BBF8"); + contentType.Variations = ContentVariation.Culture; + var p1 = contentType.PropertyTypes.First(); + p1.Variations = ContentVariation.Culture; + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! + ServiceContext.ContentTypeService.Save(contentType); + + var page = new Content("Page", -1, contentType) + { + Level = 1, + SortOrder = 1, + CreatorId = 0, + WriterId = 0, + Key = new Guid("D7B84CC9-14AE-4D92-A042-023767AD3304") + }; + + page.SetCultureName("fr1", "fr"); + page.SetCultureName("da1", "da"); + ServiceContext.ContentService.Save(page); + var versionId0 = page.VersionId; + + page.SetValue(p1.Alias, "v1fr", "fr"); + page.SetValue(p1.Alias, "v1da", "da"); + ServiceContext.ContentService.SaveAndPublish(page); + var versionId1 = page.VersionId; + + Thread.Sleep(250); + + page.SetCultureName("fr2", "fr"); + page.SetValue(p1.Alias, "v2fr", "fr"); + ServiceContext.ContentService.SaveAndPublish(page, "fr"); + var versionId2 = page.VersionId; + + Thread.Sleep(250); + + page.SetCultureName("da2", "da"); + page.SetValue(p1.Alias, "v2da", "da"); + ServiceContext.ContentService.SaveAndPublish(page, "da"); + var versionId3 = page.VersionId; + + Thread.Sleep(250); + + page.SetCultureName("fr3", "fr"); + page.SetCultureName("da3", "da"); + page.SetValue(p1.Alias, "v3fr", "fr"); + page.SetValue(p1.Alias, "v3da", "da"); + ServiceContext.ContentService.SaveAndPublish(page); + var versionId4 = page.VersionId; + + // now get all versions + + var versions = ServiceContext.ContentService.GetVersions(page.Id).ToArray(); + + Assert.AreEqual(5, versions.Length); + + // current version + Assert.AreEqual(versionId4, versions[0].VersionId); + Assert.AreEqual(versionId3, versions[0].PublishedVersionId); + // published version + Assert.AreEqual(versionId3, versions[1].VersionId); + Assert.AreEqual(versionId3, versions[1].PublishedVersionId); + // previous version + Assert.AreEqual(versionId2, versions[2].VersionId); + Assert.AreEqual(versionId3, versions[2].PublishedVersionId); + // previous version + Assert.AreEqual(versionId1, versions[3].VersionId); + Assert.AreEqual(versionId3, versions[3].PublishedVersionId); + // previous version + Assert.AreEqual(versionId0, versions[4].VersionId); + Assert.AreEqual(versionId3, versions[4].PublishedVersionId); + + Assert.AreEqual("fr3", versions[4].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[3].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[2].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[1].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[0].GetPublishName("fr")); + + Assert.AreEqual("fr1", versions[4].GetCultureName("fr")); + Assert.AreEqual("fr2", versions[3].GetCultureName("fr")); + Assert.AreEqual("fr2", versions[2].GetCultureName("fr")); + Assert.AreEqual("fr3", versions[1].GetCultureName("fr")); + Assert.AreEqual("fr3", versions[0].GetCultureName("fr")); + + Assert.AreEqual("da3", versions[4].GetPublishName("da")); + Assert.AreEqual("da3", versions[3].GetPublishName("da")); + Assert.AreEqual("da3", versions[2].GetPublishName("da")); + Assert.AreEqual("da3", versions[1].GetPublishName("da")); + Assert.AreEqual("da3", versions[0].GetPublishName("da")); + + Assert.AreEqual("da1", versions[4].GetCultureName("da")); + Assert.AreEqual("da1", versions[3].GetCultureName("da")); + Assert.AreEqual("da2", versions[2].GetCultureName("da")); + Assert.AreEqual("da3", versions[1].GetCultureName("da")); + Assert.AreEqual("da3", versions[0].GetCultureName("da")); + + // all versions have the same publish infos + for (var i = 0; i < 5; i++) + { + Assert.AreEqual(versions[0].PublishDate, versions[i].PublishDate); + Assert.AreEqual(versions[0].GetPublishDate("fr"), versions[i].GetPublishDate("fr")); + Assert.AreEqual(versions[0].GetPublishDate("da"), versions[i].GetPublishDate("da")); + } + + for (var i = 0; i < 5; i++) + { + Console.Write("[{0}] ", i); + Console.WriteLine(versions[i].UpdateDate.ToString("O").Substring(11)); + Console.WriteLine(" fr: {0}", versions[i].GetUpdateDate("fr")?.ToString("O").Substring(11)); + Console.WriteLine(" da: {0}", versions[i].GetUpdateDate("da")?.ToString("O").Substring(11)); + } + Console.WriteLine("-"); + + // for all previous versions, UpdateDate is the published date + + Assert.AreEqual(versions[4].UpdateDate, versions[4].GetUpdateDate("fr")); + Assert.AreEqual(versions[4].UpdateDate, versions[4].GetUpdateDate("da")); + + Assert.AreEqual(versions[3].UpdateDate, versions[3].GetUpdateDate("fr")); + Assert.AreEqual(versions[4].UpdateDate, versions[3].GetUpdateDate("da")); + + Assert.AreEqual(versions[3].UpdateDate, versions[2].GetUpdateDate("fr")); + Assert.AreEqual(versions[2].UpdateDate, versions[2].GetUpdateDate("da")); + + // for the published version, UpdateDate is the published date + + Assert.AreEqual(versions[1].UpdateDate, versions[1].GetUpdateDate("fr")); + Assert.AreEqual(versions[1].UpdateDate, versions[1].GetUpdateDate("da")); + Assert.AreEqual(versions[1].PublishDate, versions[1].UpdateDate); + + // for the current version, things are different + // UpdateDate is the date it was last saved + + Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("fr")); + Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("da")); + + // so if we save again... + + page.SetCultureName("fr4", "fr"); + //page.SetCultureName("da4", "da"); + page.SetValue(p1.Alias, "v4fr", "fr"); + page.SetValue(p1.Alias, "v4da", "da"); + ServiceContext.ContentService.Save(page); + var versionId5 = page.VersionId; + + versions = ServiceContext.ContentService.GetVersions(page.Id).ToArray(); + + // we just update the current version + Assert.AreEqual(5, versions.Length); + Assert.AreEqual(versionId4, versionId5); + + for (var i = 0; i < 5; i++) + { + Console.Write("[{0}] ", i); + Console.WriteLine(versions[i].UpdateDate.ToString("O").Substring(11)); + Console.WriteLine(" fr: {0}", versions[i].GetUpdateDate("fr")?.ToString("O").Substring(11)); + Console.WriteLine(" da: {0}", versions[i].GetUpdateDate("da")?.ToString("O").Substring(11)); + } + Console.WriteLine("-"); + + // alas, at the moment we do *not* properly track 'dirty' for cultures, meaning + // that we cannot synchronize dates the way we do with publish dates - and so this + // would fail - the version UpdateDate is greater than the cultures'. + //Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("fr")); + //Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("da")); + + // now roll french back to its very first version + page.CopyFrom(versions[4], "fr"); // only the pure FR values + page.CopyFrom(versions[4], null); // so, must explicitly do the INVARIANT values too + page.SetCultureName(versions[4].GetPublishName("fr"), "fr"); + ServiceContext.ContentService.Save(page); + + // and voila, rolled back! + Assert.AreEqual(versions[4].GetPublishName("fr"), page.GetCultureName("fr")); + Assert.AreEqual(versions[4].GetValue(p1.Alias, "fr"), page.GetValue(p1.Alias, "fr")); + + // note that rolling back invariant values means we also rolled back... DA... at least partially + // bah? + } + [Test] public void Can_Save_Lazy_Content() { From ab34deb726cde40f94b59f866920190e2bbd7913 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 17 Oct 2018 20:37:15 +0200 Subject: [PATCH 137/585] add diff --- .../rollback/rollback.controller.js | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 24fab8f30a..784cb7621f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -65,8 +65,8 @@ console.log("version", version); contentResource.getRollbackVersion(version.versionId) .then(function(data){ - console.log(data); - //createDiff(vm.currentVersion, data); + const previousVersion = data; + createDiff(vm.currentVersion, previousVersion); }); } @@ -78,7 +78,6 @@ return contentResource.getRollbackVersions(nodeId, culture) .then(function(data){ - console.log("new", data); vm.previousVersions = data; }); } @@ -92,42 +91,24 @@ */ function createDiff(currentVersion, previousVersion) { - // fake load version - vm.diff = {}; vm.diff.properties = []; - var oldVersion = { - "id": 2, - "name": "Foride", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content Test" - } - ] - }; - // find diff in name - vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, oldVersion.name); + vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, previousVersion.name); - // find diff in properties - angular.forEach(vm.currentVersion.properties, - function(newProperty, index){ - var oldProperty = oldVersion.properties[index]; + // extract all properties from the tabs and create new object for the diff + vm.currentVersion.tabs.forEach((tab, tabIndex) => { + tab.properties.forEach((property, propertyIndex) => { + var oldProperty = previousVersion.tabs[tabIndex].properties[propertyIndex]; var diffProperty = { - "alias": newProperty.alias, - "label": newProperty.label, - "diff": JsDiff.diffWords(newProperty.value, oldProperty.value) + "alias": property.alias, + "label": property.label, + "diff": JsDiff.diffWords(property.value, oldProperty.value) }; vm.diff.properties.push(diffProperty); }); + }); } From bdf474f111dcb3b559e14d55cb51518aa5efdbfe Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 09:06:46 +0100 Subject: [PATCH 138/585] Typo in lazyload JS path for AuthUpgrade page --- src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml index 01afc9f2ec..3c79d5458c 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml @@ -72,7 +72,7 @@ @*And finally we can load in our angular app*@ - + From 3f82cd7677ebadbfa42d96d872dca3b5194316c7 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 09:13:27 +0100 Subject: [PATCH 139/585] Update get rollback version to use a culture in the resource --- .../src/common/resources/content.resource.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 34a61cca91..bd64a2fdd8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -835,7 +835,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersions", { - contentId: contentId, culture: culture + contentId: contentId, + culture: culture }) ), "Failed to get rollback versions for content item with id " + contentId @@ -853,21 +854,23 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * ##usage *
-          * contentResource.getRollbackVersion(versionId)
+          * contentResource.getRollbackVersion(versionId, culture)
           *    .then(function(version) {
           *        alert('its here!');
           *    });
           * 
* * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant * @returns {Promise} resourcePromise object containing the url. * */ - getRollbackVersion: function (versionId) { + getRollbackVersion: function (versionId, culture) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersion", { - versionId: versionId + versionId: versionId, + culture: culture }) ), "Failed to get version for content item with id " + versionId From 813f3f8d84760e13fccbb9581b666aa41928f4ba Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 10:21:13 +0200 Subject: [PATCH 140/585] a language should always be selected when a node has variants --- .../src/views/common/infiniteeditors/rollback/rollback.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 70dd189328..5fb618eb78 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -26,7 +26,6 @@ ng-model="vm.selectedLanguage" ng-options="variant as variant.language.name for variant in model.node.variants track by variant.language.culture" ng-change="vm.changeLanguage(vm.selectedLanguage)"> -
From fd0831618975583a712024fba25bf871920b4cfd Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 11:10:27 +0200 Subject: [PATCH 141/585] wire up rollback button --- .../src/common/resources/content.resource.js | 40 ++++++++++++++++--- .../rollback/rollback.controller.js | 35 +++++++++++----- .../infiniteeditors/rollback/rollback.html | 3 +- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index bd64a2fdd8..71cbe3b8d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -828,7 +828,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @param {Int} id Id of node * @param {Int} culture if provided, the results will be for this specific culture/variant - * @returns {Promise} resourcePromise object containing the url. + * @returns {Promise} resourcePromise object containing the versions * */ getRollbackVersions: function (contentId, culture) { @@ -843,7 +843,6 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ); }, - /** * @ngdoc method * @name umbraco.resources.contentResource#getRollbackVersion @@ -862,7 +861,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @param {Int} versionId The version Id * @param {Int} culture if provided, the results will be for this specific culture/variant - * @returns {Promise} resourcePromise object containing the url. + * @returns {Promise} resourcePromise object containing the version * */ getRollbackVersion: function (versionId, culture) { @@ -875,10 +874,41 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to get version for content item with id " + versionId ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#rollback + * @methodOf umbraco.resources.contentResource + * + * @description + * Roll backs a content item to a previous version + * + * ##usage + *
+          * contentResource.rollback(contentId, versionId, culture)
+          *    .then(function() {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id Id of node + * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object + * + */ + rollback: function (contentId, versionId, culture) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostRollbackContent", { + contentId: contentId, versionId:versionId, culture:culture + }) + ), + "Failed to roll back content item with id " + contentId + ); } - - }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 784cb7621f..2aa628d5ef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -65,8 +65,9 @@ console.log("version", version); contentResource.getRollbackVersion(version.versionId) .then(function(data){ - const previousVersion = data; - createDiff(vm.currentVersion, previousVersion); + vm.previousVersion = data; + vm.previousVersion.versionId = version.versionId; + createDiff(vm.currentVersion, vm.previousVersion); }); } @@ -82,10 +83,6 @@ }); } - function rollback() { - console.log("rollback"); - } - /** * This will load in a new version */ @@ -95,10 +92,10 @@ vm.diff.properties = []; // find diff in name - vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, previousVersion.name); + vm.diff.name = JsDiff.diffWords(currentVersion.name, previousVersion.name); // extract all properties from the tabs and create new object for the diff - vm.currentVersion.tabs.forEach((tab, tabIndex) => { + currentVersion.tabs.forEach((tab, tabIndex) => { tab.properties.forEach((property, propertyIndex) => { var oldProperty = previousVersion.tabs[tabIndex].properties[propertyIndex]; var diffProperty = { @@ -112,9 +109,27 @@ } - function submit(model) { + function rollback() { + + vm.rollbackButtonState = "busy"; + + const nodeId = $scope.model.node.id; + const versionId = vm.previousVersion.versionId; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.rollback(nodeId, versionId, culture) + .then(data => { + vm.rollbackButtonState = "success"; + submit(); + }, error => { + vm.rollbackButtonState = "error"; + }); + + } + + function submit() { if($scope.model.submit) { - $scope.model.submit(model); + $scope.model.submit($scope.model.submit); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 5fb618eb78..8c6c1f6de5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -91,8 +91,9 @@ + action="vm.rollback()"> From 15f5e9a98e801395124e949a564989aa4baa8e8f Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 18 Oct 2018 11:25:05 +0200 Subject: [PATCH 142/585] Typo in lazyload JS path for AuthUpgrade page --- src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml index 01afc9f2ec..3c79d5458c 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml @@ -72,7 +72,7 @@ @*And finally we can load in our angular app*@ - + From 6cd9102fac2b28d056c7fd5573f78e0c541b9948 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 18 Oct 2018 21:12:55 +1100 Subject: [PATCH 143/585] Gets publish names tracking changes, adds unit tests --- src/Umbraco.Core/Models/Content.cs | 22 ++++- src/Umbraco.Core/Models/CultureName.cs | 21 ++++- .../Models/CultureNameCollection.cs | 6 +- src/Umbraco.Tests/Models/ContentTests.cs | 40 ++++++++- .../Services/ContentServiceTests.cs | 86 +++++++++++++++++++ 5 files changed, 164 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 2ee6762e61..eb95804c2c 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -88,6 +89,7 @@ namespace Umbraco.Core.Models public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); + public readonly PropertyInfo PublishNamesSelector = ExpressionHelper.GetPropertyInfo>(x => x.PublishNames); } /// @@ -222,7 +224,7 @@ namespace Umbraco.Core.Models public bool IsCulturePublished(string culture) // just check _publishInfos // a non-available culture could not become published anyways - => _publishInfos != null && _publishInfos.Contains(culture); + => _publishInfos != null && _publishInfos.Contains(culture); /// public bool WasCulturePublished(string culture) @@ -268,9 +270,10 @@ namespace Umbraco.Core.Models throw new ArgumentNullOrEmptyException(nameof(culture)); if (_publishInfos == null) + { _publishInfos = new CultureNameCollection(); - - //TODO: Track changes? + _publishInfos.CollectionChanged += PublishNamesCollectionChanged; + } _publishInfos.AddOrUpdate(culture, name, date); } @@ -314,6 +317,16 @@ namespace Umbraco.Core.Models } } + /// + /// Event handler for when the culture names collection is modified + /// + /// + /// + private void PublishNamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.PublishNamesSelector); + } + [IgnoreDataMember] public int PublishedVersionId { get; internal set; } @@ -430,7 +443,8 @@ namespace Umbraco.Core.Models // take care of the published state _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; - // take care of publish infos + // Make a copy of the _publishInfos, this is purely so that we can detect + // if this entity's previous culture publish state (regardless of the rememberDirty flag) _publishInfosOrig = _publishInfos == null ? null : new CultureNameCollection(_publishInfos); diff --git a/src/Umbraco.Core/Models/CultureName.cs b/src/Umbraco.Core/Models/CultureName.cs index 64db50c36d..d683f07b49 100644 --- a/src/Umbraco.Core/Models/CultureName.cs +++ b/src/Umbraco.Core/Models/CultureName.cs @@ -14,12 +14,27 @@ namespace Umbraco.Core.Models private string _name; private static readonly Lazy Ps = new Lazy(); - public CultureName(string culture, string name, DateTime date) + /// + /// Used for cloning without change tracking + /// + /// + /// + /// + private CultureName(string culture, string name, DateTime date) + : this(culture) + { + _name = name; + _date = date; + } + + /// + /// Constructor + /// + /// + public CultureName(string culture) { if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("message", nameof(culture)); Culture = culture; - _name = name; - _date = date; } public string Culture { get; private set; } diff --git a/src/Umbraco.Core/Models/CultureNameCollection.cs b/src/Umbraco.Core/Models/CultureNameCollection.cs index 3c00603c88..be6540c399 100644 --- a/src/Umbraco.Core/Models/CultureNameCollection.cs +++ b/src/Umbraco.Core/Models/CultureNameCollection.cs @@ -56,7 +56,11 @@ namespace Umbraco.Core.Models OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, found, found)); } else - Add(new CultureName(culture, name, date)); + Add(new CultureName(culture) + { + Name = name, + Date = date + }); } /// diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index a7d0f2f050..1c3e2453c3 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -43,7 +44,7 @@ namespace Umbraco.Tests.Models } [Test] - public void Variant_Names_Track_Dirty_Changes() + public void Variant_Culture_Names_Track_Dirty_Changes() { var contentType = new ContentType(-1) { Alias = "contentType" }; var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; @@ -54,21 +55,54 @@ namespace Umbraco.Tests.Models Assert.IsFalse(content.IsPropertyDirty("CultureNames")); //hasn't been changed + Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); Assert.IsTrue(content.IsPropertyDirty("CultureNames")); //now it will be changed since the collection has changed var frCultureName = content.CultureNames[langFr]; - Assert.IsFalse(frCultureName.IsPropertyDirty("Date")); //this won't be dirty because it wasn't actually updated, just created + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); content.ResetDirtyProperties(); Assert.IsFalse(content.IsPropertyDirty("CultureNames")); //it's been reset Assert.IsTrue(content.WasPropertyDirty("CultureNames")); + Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); //this will be dirty because it was already created and now has been updated + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); Assert.IsTrue(content.IsPropertyDirty("CultureNames")); //it's true now since we've updated a name } + [Test] + public void Variant_Published_Culture_Names_Track_Dirty_Changes() + { + var contentType = new ContentType(-1) { Alias = "contentType" }; + var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; + + const string langFr = "fr-FR"; + + contentType.Variations = ContentVariation.Culture; + + Assert.IsFalse(content.IsPropertyDirty("PublishNames")); //hasn't been changed + + Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date + content.SetCultureName("name-fr", langFr); + content.PublishCulture(langFr); //we've set the name, now we're publishing it + Assert.IsTrue(content.IsPropertyDirty("PublishNames")); //now it will be changed since the collection has changed + var frCultureName = content.PublishNames[langFr]; + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); + + content.ResetDirtyProperties(); + + Assert.IsFalse(content.IsPropertyDirty("PublishNames")); //it's been reset + Assert.IsTrue(content.WasPropertyDirty("PublishNames")); + + Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date + content.SetCultureName("name-fr", langFr); + content.PublishCulture(langFr); //we've set the name, now we're publishing it + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); + Assert.IsTrue(content.IsPropertyDirty("PublishNames")); //it's true now since we've updated a name + } + [Test] public void Get_Non_Grouped_Properties() { diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index f187b8b70d..4d31d8342a 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1234,6 +1234,92 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Can_Unpublish_Content_Variation() + { + // Arrange + + var langUk = new Language("en-UK") { IsDefault = true }; + var langFr = new Language("fr-FR"); + + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langUk); + + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + IContent content = new Content("content", -1, contentType); + content.SetCultureName("content-fr", langFr.IsoCode); + content.SetCultureName("content-en", langUk.IsoCode); + content.PublishCulture(langFr.IsoCode); + content.PublishCulture(langUk.IsoCode); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsFalse(content.WasCulturePublished(langFr.IsoCode)); //not persisted yet, will be false + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsFalse(content.WasCulturePublished(langUk.IsoCode)); //not persisted yet, will be false + + var published = ServiceContext.ContentService.SavePublishing(content); + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.IsTrue(published.Success); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.WasCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(content.WasCulturePublished(langUk.IsoCode)); + + var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); + Assert.IsTrue(unpublished.Success); + + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + //this is slightly confusing but this will be false because this method is used for checking the state of the current model, + //but the state on the model has changed with the above Unpublish call + Assert.IsFalse(content.WasCulturePublished(langFr.IsoCode)); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + //this is slightly confusing but this will be false because this method is used for checking the state of a current model, + //but we've re-fetched from the database + Assert.IsFalse(content.WasCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(content.WasCulturePublished(langUk.IsoCode)); + + } + + [Test] + public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures() + { + // Arrange + + var langUk = new Language("en-UK") { IsDefault = true }; + var langFr = new Language("fr-FR"); + + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langUk); + + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + IContent content = new Content("content", -1, contentType); + content.SetCultureName("content-fr", langFr.IsoCode); + content.PublishCulture(langFr.IsoCode); + var published = ServiceContext.ContentService.SavePublishing(content); + //audit log will only show that french was published + var lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); + Assert.AreEqual($"Published culture fr-fr", lastLog.Comment); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + content.SetCultureName("content-en", langUk.IsoCode); + content.PublishCulture(langUk.IsoCode); + published = ServiceContext.ContentService.SavePublishing(content); + //audit log will only show that english was published + lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); + Assert.AreEqual($"Published culture en-uk", lastLog.Comment); + } + [Test] public void Can_Publish_Content_1() { From f1ca32ea5406301dd72fbef19ae7067ea56c5ab4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 18 Oct 2018 22:47:12 +1100 Subject: [PATCH 144/585] Cleaning up the log/audit trail usage, removing duplicate and unecessary entries and adding new cols --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../Upgrade/V_8_0_0/AddLogTableColumns.cs | 24 +++++++ src/Umbraco.Core/Models/AuditItem.cs | 10 ++- src/Umbraco.Core/Persistence/Dtos/LogDto.cs | 18 +++++ .../Repositories/Implement/AuditRepository.cs | 18 +++-- src/Umbraco.Core/Services/IAuditService.cs | 2 +- .../Services/Implement/AuditService.cs | 4 +- .../Services/Implement/ContentService.cs | 72 +++++++++++++------ ...peServiceBaseOfTRepositoryTItemTService.cs | 13 ++-- .../Services/Implement/DataTypeService.cs | 10 +-- .../Services/Implement/FileService.cs | 26 +++---- .../Services/Implement/LocalizationService.cs | 12 ++-- .../Services/Implement/MacroService.cs | 8 +-- .../Services/Implement/MediaService.cs | 28 ++++---- .../Services/Implement/MemberService.cs | 12 ++-- .../Services/Implement/PackagingService.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 17 files changed, 174 insertions(+), 87 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLogTableColumns.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index eeaf7533a9..4761f2a1fc 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -137,6 +137,7 @@ namespace Umbraco.Core.Migrations.Upgrade // resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}... Chain("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}"); + Chain("{8804D8E8-FE62-4E3A-B8A2-C047C2118C38}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLogTableColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLogTableColumns.cs new file mode 100644 index 0000000000..d038da2573 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLogTableColumns.cs @@ -0,0 +1,24 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddLogTableColumns : MigrationBase + { + public AddLogTableColumns(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); + + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.Log) && !x.ColumnName.InvariantEquals("entityType"))) + AddColumn("entityType"); + + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.Log) && !x.ColumnName.InvariantEquals("parameters"))) + AddColumn("parameters"); + + } + } +} diff --git a/src/Umbraco.Core/Models/AuditItem.cs b/src/Umbraco.Core/Models/AuditItem.cs index 6bfe32bd77..483548f558 100644 --- a/src/Umbraco.Core/Models/AuditItem.cs +++ b/src/Umbraco.Core/Models/AuditItem.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Models /// /// /// - public AuditItem(int objectId, string comment, AuditType type, int userId) + public AuditItem(int objectId, AuditType type, int userId, string entityType, string comment = null, string parameters = null) { DisableChangeTracking(); @@ -19,11 +19,19 @@ namespace Umbraco.Core.Models Comment = comment; AuditType = type; UserId = userId; + EntityType = entityType; + Parameters = parameters; EnableChangeTracking(); } public string Comment { get; } + + /// + public string EntityType { get; } + /// + public string Parameters { get; } + public AuditType AuditType { get; } public int UserId { get; } } diff --git a/src/Umbraco.Core/Persistence/Dtos/LogDto.cs b/src/Umbraco.Core/Persistence/Dtos/LogDto.cs index 2ecf85e87c..a362d5d50c 100644 --- a/src/Umbraco.Core/Persistence/Dtos/LogDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/LogDto.cs @@ -25,10 +25,20 @@ namespace Umbraco.Core.Persistence.Dtos [Index(IndexTypes.NonClustered, Name = "IX_umbracoLog")] public int NodeId { get; set; } + /// + /// This is the entity type associated with the log + /// + [Column("entityType")] + [Length(50)] + [NullSetting(NullSetting = NullSettings.Null)] + public string EntityType { get; set; } + + //TODO: Should we have an index on this since we allow searching on it? [Column("Datestamp")] [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime Datestamp { get; set; } + //TODO: Should we have an index on this since we allow searching on it? [Column("logHeader")] [Length(50)] public string Header { get; set; } @@ -37,5 +47,13 @@ namespace Umbraco.Core.Persistence.Dtos [NullSetting(NullSetting = NullSettings.Null)] [Length(4000)] public string Comment { get; set; } + + /// + /// Used to store additional data parameters for the log + /// + [Column("parameters")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(500)] + public string Parameters { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs index 5d386d9cb4..6c61fe7ad5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs @@ -28,20 +28,24 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Datestamp = DateTime.Now, Header = entity.AuditType.ToString(), NodeId = entity.Id, - UserId = entity.UserId + UserId = entity.UserId, + EntityType = entity.EntityType, + Parameters = entity.Parameters }); } protected override void PersistUpdatedItem(IAuditItem entity) { - // wtf?! inserting when updating?! + // inserting when updating because we never update a log entry, perhaps this should throw? Database.Insert(new LogDto { Comment = entity.Comment, Datestamp = DateTime.Now, Header = entity.AuditType.ToString(), NodeId = entity.Id, - UserId = entity.UserId + UserId = entity.UserId, + EntityType = entity.EntityType, + Parameters = entity.Parameters }); } @@ -53,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var dto = Database.First(sql); return dto == null ? null - : new AuditItem(dto.NodeId, dto.Comment, Enum.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId); + : new AuditItem(dto.NodeId, Enum.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -69,7 +73,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var dtos = Database.Fetch(sql); - return dtos.Select(x => new AuditItem(x.NodeId, x.Comment, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId)).ToArray(); + return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList(); } protected override Sql GetBaseQuery(bool isCount) @@ -160,10 +164,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement totalRecords = page.TotalItems; var items = page.Items.Select( - dto => new AuditItem(dto.Id, dto.Comment, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId)).ToArray(); + dto => new AuditItem(dto.Id, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList(); // map the DateStamp - for (var i = 0; i < items.Length; i++) + for (var i = 0; i < items.Count; i++) items[i].CreateDate = page.Items[i].Datestamp; return items; diff --git a/src/Umbraco.Core/Services/IAuditService.cs b/src/Umbraco.Core/Services/IAuditService.cs index 13d84f802e..f9b5aa2d87 100644 --- a/src/Umbraco.Core/Services/IAuditService.cs +++ b/src/Umbraco.Core/Services/IAuditService.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Services /// public interface IAuditService : IService { - void Add(AuditType type, string comment, int userId, int objectId); + void Add(AuditType type, int userId, int objectId, string entityType, string comment, string parameters = null); IEnumerable GetLogs(int objectId); IEnumerable GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null); diff --git a/src/Umbraco.Core/Services/Implement/AuditService.cs b/src/Umbraco.Core/Services/Implement/AuditService.cs index 389a2337d1..d02d7f541b 100644 --- a/src/Umbraco.Core/Services/Implement/AuditService.cs +++ b/src/Umbraco.Core/Services/Implement/AuditService.cs @@ -27,11 +27,11 @@ namespace Umbraco.Core.Services.Implement _isAvailable = new Lazy(DetermineIsAvailable); } - public void Add(AuditType type, string comment, int userId, int objectId) + public void Add(AuditType type, int userId, int objectId, string entityType, string comment, string parameters = null) { using (var scope = ScopeProvider.CreateScope()) { - _auditRepository.Save(new AuditItem(objectId, comment, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, entityType, comment, parameters)); scope.Complete(); } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index a849813b13..c4b17e1350 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -328,7 +328,7 @@ namespace Umbraco.Core.Services.Implement if (withIdentity == false) return; - Audit(AuditType.New, $"Content '{content.Name}' was created with Id {content.Id}", content.CreatorId, content.Id); + Audit(AuditType.New, content.CreatorId, content.Id, $"Content '{content.Name}' was created with Id {content.Id}"); } #endregion @@ -843,7 +843,16 @@ namespace Umbraco.Core.Services.Implement } var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); - Audit(AuditType.Save, "Saved by user", userId, content.Id); + + var culturesChanging = content.ContentType.VariesByCulture() + ? content.CultureNames.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() + : null; + + if (culturesChanging != null && culturesChanging.Count > 0) + Audit(AuditType.Save, userId, content.Id, $"Saved culture{(culturesChanging.Count > 1 ? "s" : string.Empty)} {string.Join(",", culturesChanging)}"); + else + Audit(AuditType.Save, userId, content.Id); + scope.Complete(); } @@ -883,7 +892,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); } scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); - Audit(AuditType.Save, "Bulk-saved by user", userId == -1 ? 0 : userId, Constants.System.Root); + Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Saved multiple content"); scope.Complete(); } @@ -988,14 +997,14 @@ namespace Umbraco.Core.Services.Implement UnpublishResultType result; if (culture == "*" || culture == null) { - Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id); + Audit(AuditType.Unpublish, userId, content.Id); result = UnpublishResultType.Success; } else { - Audit(AuditType.Unpublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id); + Audit(AuditType.Unpublish, userId, content.Id, $"Culture \"{culture}\" unpublished"); if (!content.Published) - Audit(AuditType.Unpublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id); + Audit(AuditType.Unpublish, userId, content.Id, $"Unpublished (culture \"{culture}\" is mandatory)"); result = content.Published ? UnpublishResultType.SuccessCulture : UnpublishResultType.SuccessMandatoryCulture; } scope.Complete(); @@ -1025,6 +1034,8 @@ namespace Umbraco.Core.Services.Implement var publishing = content.PublishedState == PublishedState.Publishing; var unpublishing = content.PublishedState == PublishedState.Unpublishing; + List culturesChanging = null; + using (var scope = ScopeProvider.CreateScope()) { // is the content going to end up published, or unpublished? @@ -1046,6 +1057,10 @@ namespace Umbraco.Core.Services.Implement // we may end up in a state where we won't publish nor unpublish // keep going, though, as we want to save anways } + else + { + culturesChanging = content.PublishNames.Where(x => x.IsDirty()).Select(x => x.Culture).ToList(); + } } var isNew = !content.HasIdentity; @@ -1085,6 +1100,7 @@ namespace Umbraco.Core.Services.Implement // ensure that the document can be unpublished, and unpublish // handling events, business rules, etc // note: StrategyUnpublish flips the PublishedState to Unpublishing! + // note: This unpublishes the entire document (not different variants) unpublishResult = StrategyCanUnpublish(scope, content, userId, evtMsgs); if (unpublishResult.Success) unpublishResult = StrategyUnpublish(scope, content, true, userId, evtMsgs); @@ -1122,7 +1138,7 @@ namespace Umbraco.Core.Services.Implement // events and audit scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), "Unpublished"); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); - Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id); + Audit(AuditType.Unpublish, userId, content.Id); scope.Complete(); return new PublishResult(PublishResultType.Success, evtMsgs, content); } @@ -1153,7 +1169,11 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Published, this, new PublishEventArgs(descendants, false, false), "Published"); } - Audit(AuditType.Publish, "Published by user", userId, content.Id); + if (culturesChanging != null && culturesChanging.Count > 0) + Audit(AuditType.Publish, userId, content.Id, $"Published culture{(culturesChanging.Count > 1 ? "s" : string.Empty)} {string.Join(",", culturesChanging)}"); + else + Audit(AuditType.Publish, userId, content.Id); + scope.Complete(); return publishResult; } @@ -1264,6 +1284,8 @@ namespace Umbraco.Core.Services.Implement // deal with descendants // if one fails, abort its branch var exclude = new HashSet(); + + //fixme: should be paged to not overwhelm the database (timeouts) foreach (var d in GetDescendants(document)) { // if parent is excluded, exclude document and ignore @@ -1287,7 +1309,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(TreeChanged, this, new TreeChange(document, TreeChangeTypes.RefreshBranch).ToEventArgs()); scope.Events.Dispatch(Published, this, new PublishEventArgs(publishedDocuments, false, false), "Published"); - Audit(AuditType.Publish, "Branch published by user", userId, document.Id); + Audit(AuditType.Publish, userId, document.Id, "Branch published"); scope.Complete(); } @@ -1356,7 +1378,7 @@ namespace Umbraco.Core.Services.Implement DeleteLocked(scope, content); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.Remove).ToEventArgs()); - Audit(AuditType.Delete, "Deleted by user", userId, content.Id); + Audit(AuditType.Delete, userId, content.Id); scope.Complete(); } @@ -1424,7 +1446,7 @@ namespace Umbraco.Core.Services.Implement deleteRevisionsEventArgs.CanCancel = false; scope.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs); - Audit(AuditType.Delete, "Delete (by version date) by user", userId, Constants.System.Root); + Audit(AuditType.Delete, userId, Constants.System.Root, "Delete (by version date)"); scope.Complete(); } @@ -1461,7 +1483,7 @@ namespace Umbraco.Core.Services.Implement _documentRepository.DeleteVersion(versionId); scope.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false,/* specificVersion:*/ versionId)); - Audit(AuditType.Delete, "Delete (by version) by user", userId, Constants.System.Root); + Audit(AuditType.Delete, userId, Constants.System.Root, "Delete (by version)"); scope.Complete(); } @@ -1506,7 +1528,7 @@ namespace Umbraco.Core.Services.Implement moveEventArgs.CanCancel = false; moveEventArgs.MoveInfoCollection = moveInfo; scope.Events.Dispatch(Trashed, this, moveEventArgs, nameof(Trashed)); - Audit(AuditType.Move, "Moved to Recycle Bin by user", userId, content.Id); + Audit(AuditType.Move, userId, content.Id, "Moved to recycle bin"); scope.Complete(); } @@ -1578,7 +1600,7 @@ namespace Umbraco.Core.Services.Implement moveEventArgs.MoveInfoCollection = moveInfo; moveEventArgs.CanCancel = false; scope.Events.Dispatch(Moved, this, moveEventArgs, nameof(Moved)); - Audit(AuditType.Move, "Moved by user", userId, content.Id); + Audit(AuditType.Move, userId, content.Id); scope.Complete(); } @@ -1675,7 +1697,7 @@ namespace Umbraco.Core.Services.Implement recycleBinEventArgs.RecycleBinEmptiedSuccessfully = true; // oh my?! scope.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs); scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs()); - Audit(AuditType.Delete, "Recycle Bin emptied by user", 0, Constants.System.RecycleBinContent); + Audit(AuditType.Delete, 0, Constants.System.RecycleBinContent, "Recycle bin emptied"); scope.Complete(); } @@ -1793,7 +1815,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(TreeChanged, this, new TreeChange(copy, TreeChangeTypes.RefreshBranch).ToEventArgs()); foreach (var x in copies) scope.Events.Dispatch(Copied, this, new CopyEventArgs(x.Item1, x.Item2, false, x.Item2.ParentId, relateToOriginal)); - Audit(AuditType.Copy, "Copy Content performed by user", userId, content.Id); + Audit(AuditType.Copy, userId, content.Id); scope.Complete(); } @@ -1824,7 +1846,15 @@ namespace Umbraco.Core.Services.Implement sendToPublishEventArgs.CanCancel = false; scope.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); - Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + + var culturesChanging = content.ContentType.VariesByCulture() + ? content.CultureNames.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() + : null; + + if (culturesChanging != null && culturesChanging.Count > 0) + Audit(AuditType.SendToPublish, userId, content.Id, $"Send To Publish for culture{(culturesChanging.Count > 1 ? "s" : string.Empty)} {string.Join(",", culturesChanging)}"); + else + Audit(AuditType.SendToPublish, content.WriterId, content.Id); } return true; @@ -1930,7 +1960,7 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents && published.Any()) scope.Events.Dispatch(Published, this, new PublishEventArgs(published, false, false), "Published"); - Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); + Audit(AuditType.Sort, userId, 0); return true; } @@ -1977,9 +2007,9 @@ namespace Umbraco.Core.Services.Implement #region Private Methods - private void Audit(AuditType type, string message, int userId, int objectId) + private void Audit(AuditType type, int userId, int objectId, string message = null) { - _auditRepository.Save(new AuditItem(objectId, message, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, Constants.ObjectTypes.Strings.Document, message)); } #endregion @@ -2311,7 +2341,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Trashed, this, new MoveEventArgs(false, moveInfos), nameof(Trashed)); scope.Events.Dispatch(TreeChanged, this, changes.ToEventArgs()); - Audit(AuditType.Delete, $"Delete Content of Type {string.Join(",", contentTypeIdsA)} performed by user", userId, Constants.System.Root); + Audit(AuditType.Delete, userId, Constants.System.Root, $"Delete content of type {string.Join(",", contentTypeIdsA)}"); scope.Complete(); } diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index a114f415cc..60677cfd81 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -423,7 +423,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; OnSaved(scope, saveEventArgs); - Audit(AuditType.Save, $"Save {typeof(TItem).Name} performed by user", userId, item.Id); + Audit(AuditType.Save, userId, item.Id); scope.Complete(); } } @@ -465,7 +465,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; OnSaved(scope, saveEventArgs); - Audit(AuditType.Save, $"Save {typeof(TItem).Name} performed by user", userId, -1); + Audit(AuditType.Save, userId, -1); scope.Complete(); } } @@ -523,7 +523,7 @@ namespace Umbraco.Core.Services.Implement deleteEventArgs.CanCancel = false; OnDeleted(scope, deleteEventArgs); - Audit(AuditType.Delete, $"Delete {typeof(TItem).Name} performed by user", userId, item.Id); + Audit(AuditType.Delete, userId, item.Id); scope.Complete(); } } @@ -576,7 +576,7 @@ namespace Umbraco.Core.Services.Implement deleteEventArgs.CanCancel = false; OnDeleted(scope, deleteEventArgs); - Audit(AuditType.Delete, $"Delete {typeof(TItem).Name} performed by user", userId, -1); + Audit(AuditType.Delete, userId, -1); scope.Complete(); } } @@ -963,9 +963,10 @@ namespace Umbraco.Core.Services.Implement #region Audit - private void Audit(AuditType type, string message, int userId, int objectId) + private void Audit(AuditType type, int userId, int objectId) { - _auditRepository.Save(new AuditItem(objectId, message, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, + ObjectTypes.GetUmbracoObjectType(ContainedObjectType).GetName())); } #endregion diff --git a/src/Umbraco.Core/Services/Implement/DataTypeService.cs b/src/Umbraco.Core/Services/Implement/DataTypeService.cs index c105b6cfe6..79ca851de9 100644 --- a/src/Umbraco.Core/Services/Implement/DataTypeService.cs +++ b/src/Umbraco.Core/Services/Implement/DataTypeService.cs @@ -353,7 +353,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs); - Audit(AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataType.Id); + Audit(AuditType.Save, userId, dataType.Id); scope.Complete(); } } @@ -398,7 +398,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs); } - Audit(AuditType.Save, "Save DataTypeDefinition performed by user", userId, -1); + Audit(AuditType.Save, userId, -1); scope.Complete(); } @@ -456,15 +456,15 @@ namespace Umbraco.Core.Services.Implement deleteEventArgs.CanCancel = false; scope.Events.Dispatch(Deleted, this, deleteEventArgs); - Audit(AuditType.Delete, "Delete DataTypeDefinition performed by user", userId, dataType.Id); + Audit(AuditType.Delete, userId, dataType.Id); scope.Complete(); } } - private void Audit(AuditType type, string message, int userId, int objectId) + private void Audit(AuditType type, int userId, int objectId) { - _auditRepository.Save(new AuditItem(objectId, message, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.DataType))); } #region Event Handlers diff --git a/src/Umbraco.Core/Services/Implement/FileService.cs b/src/Umbraco.Core/Services/Implement/FileService.cs index c3a8b790cc..f15f0d7d47 100644 --- a/src/Umbraco.Core/Services/Implement/FileService.cs +++ b/src/Umbraco.Core/Services/Implement/FileService.cs @@ -91,7 +91,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(SavedStylesheet, this, saveEventArgs); - Audit(AuditType.Save, "Save Stylesheet performed by user", userId, -1); + Audit(AuditType.Save, userId, -1, ObjectTypes.GetName(UmbracoObjectTypes.Stylesheet)); scope.Complete(); } } @@ -123,7 +123,7 @@ namespace Umbraco.Core.Services.Implement deleteEventArgs.CanCancel = false; scope.Events.Dispatch(DeletedStylesheet, this, deleteEventArgs); - Audit(AuditType.Delete, "Delete Stylesheet performed by user", userId, -1); + Audit(AuditType.Delete, userId, -1, ObjectTypes.GetName(UmbracoObjectTypes.Stylesheet)); scope.Complete(); } } @@ -215,7 +215,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(SavedScript, this, saveEventArgs); - Audit(AuditType.Save, "Save Script performed by user", userId, -1); + Audit(AuditType.Save, userId, -1, "Script"); scope.Complete(); } } @@ -247,7 +247,7 @@ namespace Umbraco.Core.Services.Implement deleteEventArgs.CanCancel = false; scope.Events.Dispatch(DeletedScript, this, deleteEventArgs); - Audit(AuditType.Delete, "Delete Script performed by user", userId, -1); + Audit(AuditType.Delete, userId, -1, "Script"); scope.Complete(); } } @@ -362,7 +362,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(SavedTemplate, this, saveEventArgs); - Audit(AuditType.Save, "Save Template performed by user", userId, template.Id); + Audit(AuditType.Save, userId, template.Id, ObjectTypes.GetName(UmbracoObjectTypes.Template)); scope.Complete(); } @@ -525,7 +525,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(SavedTemplate, this, new SaveEventArgs(template, false)); - Audit(AuditType.Save, "Save Template performed by user", userId, template.Id); + Audit(AuditType.Save, userId, template.Id, ObjectTypes.GetName(UmbracoObjectTypes.Template)); scope.Complete(); } } @@ -551,7 +551,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(SavedTemplate, this, new SaveEventArgs(templatesA, false)); - Audit(AuditType.Save, "Save Template performed by user", userId, -1); + Audit(AuditType.Save, userId, -1, ObjectTypes.GetName(UmbracoObjectTypes.Template)); scope.Complete(); } } @@ -605,7 +605,7 @@ namespace Umbraco.Core.Services.Implement args.CanCancel = false; scope.Events.Dispatch(DeletedTemplate, this, args); - Audit(AuditType.Delete, "Delete Template performed by user", userId, template.Id); + Audit(AuditType.Delete, userId, template.Id, ObjectTypes.GetName(UmbracoObjectTypes.Template)); scope.Complete(); } } @@ -788,7 +788,7 @@ namespace Umbraco.Core.Services.Implement newEventArgs.CanCancel = false; scope.Events.Dispatch(CreatedPartialView, this, newEventArgs); - Audit(AuditType.Save, $"Save {partialViewType} performed by user", userId, -1); + Audit(AuditType.Save, userId, -1, partialViewType.ToString()); scope.Complete(); } @@ -828,7 +828,7 @@ namespace Umbraco.Core.Services.Implement repository.Delete(partialView); deleteEventArgs.CanCancel = false; scope.Events.Dispatch(DeletedPartialView, this, deleteEventArgs); - Audit(AuditType.Delete, $"Delete {partialViewType} performed by user", userId, -1); + Audit(AuditType.Delete, userId, -1, partialViewType.ToString()); scope.Complete(); } @@ -860,7 +860,7 @@ namespace Umbraco.Core.Services.Implement var repository = GetPartialViewRepository(partialViewType); repository.Save(partialView); saveEventArgs.CanCancel = false; - Audit(AuditType.Save, $"Save {partialViewType} performed by user", userId, -1); + Audit(AuditType.Save, userId, -1, partialViewType.ToString()); scope.Events.Dispatch(SavedPartialView, this, saveEventArgs); scope.Complete(); @@ -1038,9 +1038,9 @@ namespace Umbraco.Core.Services.Implement #endregion - private void Audit(AuditType type, string message, int userId, int objectId) + private void Audit(AuditType type, int userId, int objectId, string entityType) { - _auditRepository.Save(new AuditItem(objectId, message, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, entityType)); } //TODO Method to change name and/or alias of view/masterpage template diff --git a/src/Umbraco.Core/Services/Implement/LocalizationService.cs b/src/Umbraco.Core/Services/Implement/LocalizationService.cs index 49a764b533..c972b949d6 100644 --- a/src/Umbraco.Core/Services/Implement/LocalizationService.cs +++ b/src/Umbraco.Core/Services/Implement/LocalizationService.cs @@ -245,7 +245,7 @@ namespace Umbraco.Core.Services.Implement EnsureDictionaryItemLanguageCallback(dictionaryItem); scope.Events.Dispatch(SavedDictionaryItem, this, new SaveEventArgs(dictionaryItem, false)); - Audit(AuditType.Save, "Save DictionaryItem performed by user", userId, dictionaryItem.Id); + Audit(AuditType.Save, "Save DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem"); scope.Complete(); } } @@ -271,7 +271,7 @@ namespace Umbraco.Core.Services.Implement deleteEventArgs.CanCancel = false; scope.Events.Dispatch(DeletedDictionaryItem, this, deleteEventArgs); - Audit(AuditType.Delete, "Delete DictionaryItem performed by user", userId, dictionaryItem.Id); + Audit(AuditType.Delete, "Delete DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem"); scope.Complete(); } @@ -384,7 +384,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(SavedLanguage, this, saveEventArgs); - Audit(AuditType.Save, "Save Language performed by user", userId, language.Id); + Audit(AuditType.Save, "Save Language", userId, language.Id, ObjectTypes.GetName(UmbracoObjectTypes.Language)); scope.Complete(); } @@ -429,14 +429,14 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(DeletedLanguage, this, deleteEventArgs); - Audit(AuditType.Delete, "Delete Language performed by user", userId, language.Id); + Audit(AuditType.Delete, "Delete Language", userId, language.Id, ObjectTypes.GetName(UmbracoObjectTypes.Language)); scope.Complete(); } } - private void Audit(AuditType type, string message, int userId, int objectId) + private void Audit(AuditType type, string message, int userId, int objectId, string entityType) { - _auditRepository.Save(new AuditItem(objectId, message, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, entityType, message)); } /// diff --git a/src/Umbraco.Core/Services/Implement/MacroService.cs b/src/Umbraco.Core/Services/Implement/MacroService.cs index fdcc8e2ee0..5176e2eb22 100644 --- a/src/Umbraco.Core/Services/Implement/MacroService.cs +++ b/src/Umbraco.Core/Services/Implement/MacroService.cs @@ -95,7 +95,7 @@ namespace Umbraco.Core.Services.Implement _macroRepository.Delete(macro); deleteEventArgs.CanCancel = false; scope.Events.Dispatch(Deleted, this, deleteEventArgs); - Audit(AuditType.Delete, "Delete Macro performed by user", userId, -1); + Audit(AuditType.Delete, userId, -1); scope.Complete(); } @@ -125,7 +125,7 @@ namespace Umbraco.Core.Services.Implement _macroRepository.Save(macro); saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs); - Audit(AuditType.Save, "Save Macro performed by user", userId, -1); + Audit(AuditType.Save, userId, -1); scope.Complete(); } @@ -150,9 +150,9 @@ namespace Umbraco.Core.Services.Implement // return MacroPropertyTypeResolver.Current.MacroPropertyTypes.FirstOrDefault(x => x.Alias == alias); //} - private void Audit(AuditType type, string message, int userId, int objectId) + private void Audit(AuditType type, int userId, int objectId) { - _auditRepository.Save(new AuditItem(objectId, message, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, "Macro")); } #region Event Handlers diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index 431e20044c..da04f41e18 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -295,7 +295,7 @@ namespace Umbraco.Core.Services.Implement if (withIdentity == false) return; - Audit(AuditType.New, $"Media '{media.Name}' was created with Id {media.Id}", media.CreatorId, media.Id); + Audit(AuditType.New, media.CreatorId, media.Id, $"Media '{media.Name}' was created with Id {media.Id}"); } #endregion @@ -778,7 +778,7 @@ namespace Umbraco.Core.Services.Implement var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, changeType).ToEventArgs()); - Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); + Audit(AuditType.Save, userId, media.Id); scope.Complete(); } @@ -821,7 +821,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Saved, this, saveEventArgs); } scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); - Audit(AuditType.Save, "Bulk Save media performed by user", userId == -1 ? 0 : userId, Constants.System.Root); + Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Bulk save media"); scope.Complete(); } @@ -855,7 +855,7 @@ namespace Umbraco.Core.Services.Implement DeleteLocked(scope, media); scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, TreeChangeTypes.Remove).ToEventArgs()); - Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); + Audit(AuditType.Delete, userId, media.Id); scope.Complete(); } @@ -924,7 +924,7 @@ namespace Umbraco.Core.Services.Implement //repository.DeleteVersions(id, versionDate); //uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate)); - //Audit(uow, AuditType.Delete, "Delete Media by version date performed by user", userId, Constants.System.Root); + //Audit(uow, AuditType.Delete, "Delete Media by version date, userId, Constants.System.Root); //uow.Complete(); } @@ -942,7 +942,7 @@ namespace Umbraco.Core.Services.Implement args.CanCancel = false; scope.Events.Dispatch(DeletedVersions, this, args); - Audit(AuditType.Delete, "Delete Media by version date performed by user", userId, Constants.System.Root); + Audit(AuditType.Delete, userId, Constants.System.Root, "Delete Media by version date"); } /// @@ -978,7 +978,7 @@ namespace Umbraco.Core.Services.Implement args.CanCancel = false; scope.Events.Dispatch(DeletedVersions, this, args); - Audit(AuditType.Delete, "Delete Media by version performed by user", userId, Constants.System.Root); + Audit(AuditType.Delete, userId, Constants.System.Root, "Delete Media by version"); scope.Complete(); } @@ -1020,7 +1020,7 @@ namespace Umbraco.Core.Services.Implement .ToArray(); scope.Events.Dispatch(Trashed, this, new MoveEventArgs(false, evtMsgs, moveInfo), nameof(Trashed)); - Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); + Audit(AuditType.Move, userId, media.Id, "Move Media to recycle bin"); scope.Complete(); } @@ -1080,7 +1080,7 @@ namespace Umbraco.Core.Services.Implement moveEventArgs.MoveInfoCollection = moveInfo; moveEventArgs.CanCancel = false; scope.Events.Dispatch(Moved, this, moveEventArgs, nameof(Moved)); - Audit(AuditType.Move, "Move Media performed by user", userId, media.Id); + Audit(AuditType.Move, userId, media.Id); scope.Complete(); } } @@ -1173,7 +1173,7 @@ namespace Umbraco.Core.Services.Implement args.CanCancel = false; scope.Events.Dispatch(EmptiedRecycleBin, this, args); scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs()); - Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, Constants.System.RecycleBinMedia); + Audit(AuditType.Delete, 0, Constants.System.RecycleBinMedia, "Empty Media recycle bin"); scope.Complete(); } @@ -1238,7 +1238,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Saved, this, args); } scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); - Audit(AuditType.Sort, "Sorting Media performed by user", userId, 0); + Audit(AuditType.Sort, userId, 0); scope.Complete(); } @@ -1250,9 +1250,9 @@ namespace Umbraco.Core.Services.Implement #region Private Methods - private void Audit(AuditType type, string message, int userId, int objectId) + private void Audit(AuditType type, int userId, int objectId, string message = null) { - _auditRepository.Save(new AuditItem(objectId, message, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Media), message)); } #endregion @@ -1434,7 +1434,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Trashed, this, new MoveEventArgs(false, moveInfos), nameof(Trashed)); scope.Events.Dispatch(TreeChanged, this, changes.ToEventArgs()); - Audit(AuditType.Delete, $"Delete Media of types {string.Join(",", mediaTypeIdsA)} performed by user", userId, Constants.System.Root); + Audit(AuditType.Delete, userId, Constants.System.Root, $"Delete Media of types {string.Join(",", mediaTypeIdsA)}"); scope.Complete(); } diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 211e30d01c..3fd714f974 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -337,7 +337,7 @@ namespace Umbraco.Core.Services.Implement if (withIdentity == false) return; - Audit(AuditType.New, $"Member '{member.Name}' was created with Id {member.Id}", member.CreatorId, member.Id); + Audit(AuditType.New, member.CreatorId, member.Id, $"Member '{member.Name}' was created with Id {member.Id}"); } #endregion @@ -843,7 +843,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs); } - Audit(AuditType.Save, "Save Member performed by user", 0, member.Id); + Audit(AuditType.Save, 0, member.Id); scope.Complete(); } @@ -884,7 +884,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs); } - Audit(AuditType.Save, "Save Member items performed by user", 0, -1); + Audit(AuditType.Save, 0, -1, "Save multiple Members"); scope.Complete(); } @@ -912,7 +912,7 @@ namespace Umbraco.Core.Services.Implement scope.WriteLock(Constants.Locks.MemberTree); DeleteLocked(scope, member, deleteEventArgs); - Audit(AuditType.Delete, "Delete Member performed by user", 0, member.Id); + Audit(AuditType.Delete, 0, member.Id); scope.Complete(); } } @@ -1089,9 +1089,9 @@ namespace Umbraco.Core.Services.Implement #region Private Methods - private void Audit(AuditType type, string message, int userId, int objectId) + private void Audit(AuditType type, int userId, int objectId, string message = null) { - _auditRepository.Save(new AuditItem(objectId, message, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Member), message)); } #endregion diff --git a/src/Umbraco.Core/Services/Implement/PackagingService.cs b/src/Umbraco.Core/Services/Implement/PackagingService.cs index 67e07e63b6..c0e8f80337 100644 --- a/src/Umbraco.Core/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Core/Services/Implement/PackagingService.cs @@ -1485,7 +1485,7 @@ namespace Umbraco.Core.Services.Implement private void Audit(AuditType type, string message, int userId, int objectId) { - _auditRepository.Save(new AuditItem(objectId, message, type, userId)); + _auditRepository.Save(new AuditItem(objectId, type, userId, "Package", message)); } #endregion diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0eba543e0d..dc5ca5e2aa 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -353,6 +353,7 @@ + From c3e2d2a821dfc5aa7163adec415bfbf9d8e46ca1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 18 Oct 2018 22:52:49 +1100 Subject: [PATCH 145/585] Cleaning up the log/audit trail usage, removing duplicate and unecessary entries and adding new cols --- .../Components/RelateOnCopyComponent.cs | 5 +-- .../Components/RelateOnTrashComponent.cs | 16 ++++++---- src/Umbraco.Core/Models/IAuditItem.cs | 10 ++++++ .../Cache/DefaultCachePolicyTests.cs | 16 +++++----- .../Cache/FullDataSetCachePolicyTests.cs | 32 +++++++++---------- .../Cache/SingleItemsOnlyCachePolicyTests.cs | 6 ++-- .../Repositories/AuditRepositoryTest.cs | 18 +++++------ src/Umbraco.Web.UI.Client/package-lock.json | 15 +++------ src/Umbraco.Web/_Legacy/Packager/Installer.cs | 4 +-- .../PackageInstance/InstalledPackage.cs | 2 +- 10 files changed, 65 insertions(+), 59 deletions(-) diff --git a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs index 4ebd309e9f..bc66dccd31 100644 --- a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs @@ -37,8 +37,9 @@ namespace Umbraco.Core.Components Current.Services.AuditService.Add( AuditType.Copy, - $"Copied content with Id: '{e.Copy.Id}' related to original content with Id: '{e.Original.Id}'", - e.Copy.WriterId, e.Copy.Id); + e.Copy.WriterId, + e.Copy.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document), + $"Copied content with Id: '{e.Copy.Id}' related to original content with Id: '{e.Original.Id}'"); } } } diff --git a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs index fffae85501..8bcce50c68 100644 --- a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs @@ -82,11 +82,12 @@ namespace Umbraco.Core.Components relationService.Save(relation); Current.Services.AuditService.Add(AuditType.Delete, + item.Entity.WriterId, + item.Entity.Id, + ObjectTypes.GetName(UmbracoObjectTypes.Document), string.Format(textService.Localize( "recycleBin/contentTrashed"), - item.Entity.Id, originalParentId), - item.Entity.WriterId, - item.Entity.Id); + item.Entity.Id, originalParentId)); } } } @@ -120,11 +121,12 @@ namespace Umbraco.Core.Components var relation = new Relation(originalParentId, item.Entity.Id, relationType); relationService.Save(relation); Current.Services.AuditService.Add(AuditType.Delete, - string.Format(textService.Localize( + item.Entity.CreatorId, + item.Entity.Id, + ObjectTypes.GetName(UmbracoObjectTypes.Media), + string.Format(textService.Localize( "recycleBin/mediaTrashed"), - item.Entity.Id, originalParentId), - item.Entity.CreatorId, - item.Entity.Id); + item.Entity.Id, originalParentId)); } } } diff --git a/src/Umbraco.Core/Models/IAuditItem.cs b/src/Umbraco.Core/Models/IAuditItem.cs index 9416e2a055..c1dfd99dbb 100644 --- a/src/Umbraco.Core/Models/IAuditItem.cs +++ b/src/Umbraco.Core/Models/IAuditItem.cs @@ -6,6 +6,16 @@ namespace Umbraco.Core.Models public interface IAuditItem : IEntity { string Comment { get; } + + /// + /// The entity type for the log entry + /// + string EntityType { get; } + + /// + /// Optional additional data parameters for the log + /// + string Parameters { get; } AuditType AuditType { get; } int UserId { get; } } diff --git a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs index a8021055a9..37488600c7 100644 --- a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Cache var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); - var unused = defaultPolicy.Get(1, id => new AuditItem(1, "blah", AuditType.Copy, 123), o => null); + var unused = defaultPolicy.Get(1, id => new AuditItem(1, AuditType.Copy, 123, "test", "blah"), o => null); Assert.IsTrue(isCached); } @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Cache public void Get_Single_From_Cache() { var cache = new Mock(); - cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, "blah", AuditType.Copy, 123)); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, AuditType.Copy, 123, "test", "blah")); var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); @@ -71,8 +71,8 @@ namespace Umbraco.Tests.Cache var unused = defaultPolicy.GetAll(new object[] {}, ids => new[] { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) + new AuditItem(1, AuditType.Copy, 123, "test", "blah"), + new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }); Assert.AreEqual(2, cached.Count); @@ -84,8 +84,8 @@ namespace Umbraco.Tests.Cache var cache = new Mock(); cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new[] { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) + new AuditItem(1, AuditType.Copy, 123, "test", "blah"), + new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }); var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); @@ -108,7 +108,7 @@ namespace Umbraco.Tests.Cache var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); try { - defaultPolicy.Update(new AuditItem(1, "blah", AuditType.Copy, 123), item => throw new Exception("blah!")); + defaultPolicy.Update(new AuditItem(1, AuditType.Copy, 123, "test", "blah"), item => throw new Exception("blah!")); } catch { @@ -134,7 +134,7 @@ namespace Umbraco.Tests.Cache var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); try { - defaultPolicy.Delete(new AuditItem(1, "blah", AuditType.Copy, 123), item => throw new Exception("blah!")); + defaultPolicy.Delete(new AuditItem(1, AuditType.Copy, 123, "test", "blah"), item => throw new Exception("blah!")); } catch { diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs index a275a44964..404587bcfa 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -32,8 +32,8 @@ namespace Umbraco.Tests.Cache { var getAll = new[] { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) + new AuditItem(1, AuditType.Copy, 123, "test", "blah"), + new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }; var isCached = false; @@ -47,7 +47,7 @@ namespace Umbraco.Tests.Cache var policy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); - var unused = policy.Get(1, id => new AuditItem(1, "blah", AuditType.Copy, 123), ids => getAll); + var unused = policy.Get(1, id => new AuditItem(1, AuditType.Copy, 123, "test", "blah"), ids => getAll); Assert.IsTrue(isCached); } @@ -56,12 +56,12 @@ namespace Umbraco.Tests.Cache { var getAll = new[] { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) + new AuditItem(1, AuditType.Copy, 123, "test", "blah"), + new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }; var cache = new Mock(); - cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, "blah", AuditType.Copy, 123)); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, AuditType.Copy, 123, "test", "blah")); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); @@ -114,8 +114,8 @@ namespace Umbraco.Tests.Cache { var getAll = new[] { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) + new AuditItem(1, AuditType.Copy, 123, "test", "blah"), + new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }; var cached = new List(); @@ -149,8 +149,8 @@ namespace Umbraco.Tests.Cache cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => new DeepCloneableList(ListCloneBehavior.CloneOnce) { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) + new AuditItem(1, AuditType.Copy, 123, "test", "blah"), + new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); @@ -164,8 +164,8 @@ namespace Umbraco.Tests.Cache { var getAll = new[] { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) + new AuditItem(1, AuditType.Copy, 123, "test", "blah"), + new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }; var cacheCleared = false; @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Cache var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); try { - defaultPolicy.Update(new AuditItem(1, "blah", AuditType.Copy, 123), item => { throw new Exception("blah!"); }); + defaultPolicy.Update(new AuditItem(1, AuditType.Copy, 123, "test", "blah"), item => { throw new Exception("blah!"); }); } catch { @@ -196,8 +196,8 @@ namespace Umbraco.Tests.Cache { var getAll = new[] { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) + new AuditItem(1, AuditType.Copy, 123, "test", "blah"), + new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }; var cacheCleared = false; @@ -211,7 +211,7 @@ namespace Umbraco.Tests.Cache var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); try { - defaultPolicy.Delete(new AuditItem(1, "blah", AuditType.Copy, 123), item => { throw new Exception("blah!"); }); + defaultPolicy.Delete(new AuditItem(1, AuditType.Copy, 123, "test", "blah"), item => { throw new Exception("blah!"); }); } catch { diff --git a/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs index 9ab98bda7e..1c2227f79b 100644 --- a/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs @@ -41,8 +41,8 @@ namespace Umbraco.Tests.Cache var unused = defaultPolicy.GetAll(new object[] { }, ids => new[] { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) + new AuditItem(1, AuditType.Copy, 123, "test", "blah"), + new AuditItem(2, AuditType.Copy, 123, "test", "blah2") }); Assert.AreEqual(0, cached.Count); @@ -62,7 +62,7 @@ namespace Umbraco.Tests.Cache var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); - var unused = defaultPolicy.Get(1, id => new AuditItem(1, "blah", AuditType.Copy, 123), ids => null); + var unused = defaultPolicy.Get(1, id => new AuditItem(1, AuditType.Copy, 123, "test", "blah"), ids => null); Assert.IsTrue(isCached); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs index 6953634a31..eb85656ee4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); - repo.Save(new AuditItem(-1, "This is a System audit trail", AuditType.System, -1)); + repo.Save(new AuditItem(-1, AuditType.System, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "This is a System audit trail")); var dtos = scope.Database.Fetch("WHERE id > -1"); @@ -46,8 +46,8 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1)); - repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1)); + repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} created")); + repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} published")); } scope.Complete(); @@ -74,8 +74,8 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1)); - repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1)); + repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} created")); + repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} published")); } scope.Complete(); @@ -117,8 +117,8 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1)); - repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1)); + repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} created")); + repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} published")); } scope.Complete(); @@ -148,8 +148,8 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, "Content created", AuditType.New, -1)); - repo.Save(new AuditItem(i, "Content published", AuditType.Publish, -1)); + repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "Content created")); + repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "Content published")); } scope.Complete(); diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 4b8cc3e6c2..6cce6d7708 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -5505,8 +5505,7 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", @@ -5516,8 +5515,7 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5634,8 +5632,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5647,7 +5644,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5773,8 +5769,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5786,7 +5781,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5908,7 +5902,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/src/Umbraco.Web/_Legacy/Packager/Installer.cs b/src/Umbraco.Web/_Legacy/Packager/Installer.cs index 2581e3ad48..ae6b3e0a11 100644 --- a/src/Umbraco.Web/_Legacy/Packager/Installer.cs +++ b/src/Umbraco.Web/_Legacy/Packager/Installer.cs @@ -323,8 +323,8 @@ namespace umbraco.cms.businesslogic.packager if (_currentUserId > -1) { Current.Services.AuditService.Add(AuditType.PackagerInstall, - string.Format("Package '{0}' installed. Package guid: {1}", insPack.Data.Name, insPack.Data.PackageGuid), - _currentUserId, -1); + _currentUserId, + -1, "Package", string.Format("Package '{0}' installed. Package guid: {1}", insPack.Data.Name, insPack.Data.PackageGuid)); } insPack.Save(); diff --git a/src/Umbraco.Web/_Legacy/Packager/PackageInstance/InstalledPackage.cs b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/InstalledPackage.cs index c16afa0b84..ae4d23aa9a 100644 --- a/src/Umbraco.Web/_Legacy/Packager/PackageInstance/InstalledPackage.cs +++ b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/InstalledPackage.cs @@ -67,7 +67,7 @@ namespace umbraco.cms.businesslogic.packager { public void Delete(int userId) { - Current.Services.AuditService.Add(AuditType.PackagerUninstall, string.Format("Package '{0}' uninstalled. Package guid: {1}", Data.Name, Data.PackageGuid), userId, -1); + Current.Services.AuditService.Add(AuditType.PackagerUninstall, userId, -1, "Package", string.Format("Package '{0}' uninstalled. Package guid: {1}", Data.Name, Data.PackageGuid)); Delete(); } From b2138d6376d3582baa175f819eefafc7bf7b43d8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 14:06:21 +0200 Subject: [PATCH 146/585] reload content after rollback --- .../directives/components/content/edit.controller.js | 11 +++++++++++ .../content/umbcontentnodeinfo.directive.js | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 98e02f3d55..6670a41ac4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -103,6 +103,17 @@ loadContent(); } })); + + evts.push(eventsService.on("editors.content.reload", function (name, args) { + // if this content item uses the updated doc type we need to reload the content item + if(args && args.node && args.node.key === $scope.content.key) { + $scope.page.loading = true; + loadContent().then(function() { + $scope.page.loading = false; + }); + } + })); + } /** diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 327d99a8c6..e2292c50d5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -106,6 +106,8 @@ var rollback = { node: scope.node, submit: function(model) { + const args = { node: scope.node }; + eventsService.emit("editors.content.reload", args); editorService.close(); }, close: function() { From a40a6e8f35185edfd1cb3bd422a090187969b12c Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 13:13:11 +0100 Subject: [PATCH 147/585] WIP: Adds logging & audit message Fixes the filtering & dates to display in the version list --- src/Umbraco.Web/Editors/ContentController.cs | 77 ++++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index ad2734e8cf..b37bf82797 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1708,16 +1708,17 @@ namespace Umbraco.Web.Editors var rollbackVersions = new List(); //Return a list of all versions of a specific content node - var versions = Services.ContentService.GetVersions(contentId); - + //First item is our current item/state (cant rollback to ourselves) + var versions = Services.ContentService.GetVersions(contentId).Skip(1); + //Not all nodes are variants & thus culture can be null //Only filter the collection - //if(cultureName != null) - //{ - // versions = versions.Where(x => x.PublishDate == x.GetPublishDate(culture)); - //} + if (culture != null) + { + versions = versions.Where(x => x.UpdateDate == x.GetUpdateDate(culture)); + } - foreach(var version in versions) + foreach (var version in versions) { var rollbackVersion = new RollbackVersion(); @@ -1725,22 +1726,12 @@ namespace Umbraco.Web.Editors rollbackVersion.VersionId = version.VersionId; //Date of version - var cultureDate = version.GetPublishDate(culture); - if (cultureDate.HasValue) - { - rollbackVersion.VersionDate = cultureDate.Value; - } - else - { - rollbackVersion.VersionDate = version.UpdateDate; - } - - //Name of publisher - //TODO: Reviewer would this extra info be expensive? - var publisherId = version.PublisherId; - var userId = version.PublisherId.HasValue ? version.PublisherId.Value : version.WriterId; - var publisher = Services.UserService.GetUserById(userId); - rollbackVersion.VersionAuthorName = publisher.Name; + rollbackVersion.VersionDate = version.UpdateDate; + + //Name of writer/publisher/user + var writerId = version.WriterId; + var user = Services.UserService.GetUserById(writerId); + rollbackVersion.VersionAuthorName = user.Name; rollbackVersions.Add(rollbackVersion); } @@ -1768,8 +1759,10 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*") { - //TODO: Do we log something - so there is a trail in the logs - //Of who performed the rollback of what document, time + var userId = Security.GetUserId().ResultOr(0); + + Logger.Info("User ID {UserId} is attempting to rollback content '{ContentId}' to version '{VersionId}'", userId, contentId, versionId); + Services.AuditService.Add(AuditType.RollBack, "YO YO YO IM ROLLING BACK", userId, contentId); //Get the current copy of the node var content = Services.ContentService.GetById(contentId); @@ -1778,16 +1771,38 @@ namespace Umbraco.Web.Editors var version = Services.ContentService.GetVersion(versionId); //Copy the changes from the version - content.CopyFrom(version); - - //Save & Publish the update - var publishResult = Services.ContentService.SaveAndPublish(content, culture, Security.GetUserId().ResultOr(0)); - if (publishResult.Success == false) + content.CopyFrom(version, culture); + + //Save the update + var saveResult = Services.ContentService.Save(content, userId); + if(saveResult.Success == false) { + Logger.Error("Unable to rollback content '{ContentId}' to version '{VersionId}'", contentId, versionId); + var notificationModel = new SimpleNotificationModel(); - AddMessageForPublishStatus(publishResult, notificationModel); + + switch (saveResult.Result) + { + case OperationResultType.Failed: + case OperationResultType.FailedCannot: + case OperationResultType.FailedExceptionThrown: + case OperationResultType.NoOperation: + default: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + null); //TODO: There is no specific failed to save error message AFAIK + break; + case OperationResultType.FailedCancelledByEvent: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationCancelledHeader"), + Services.TextService.Localize("speechBubbles/operationCancelledText")); + break; + } + return Request.CreateValidationErrorResponse(notificationModel); } + + Logger.Info("User ID {UserId} rolled back content '{ContentId}' to version '{VersionId}'", userId, contentId, versionId); //return ok return Request.CreateResponse(HttpStatusCode.OK); From 510666cd7e7b35ae7852fd3d033eda3443e87353 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 18 Oct 2018 14:16:50 +0200 Subject: [PATCH 148/585] ContentService.GetPagedChildren is broken? --- .../Services/ContentServiceTests.cs | 102 ++++++++++++++++++ .../TestHelpers/TestWithDatabaseBase.cs | 2 + 2 files changed, 104 insertions(+) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index a82ccd2a0e..ef34f4bc24 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -25,6 +25,7 @@ using Umbraco.Core.Services.Implement; using Umbraco.Tests.Testing; using Umbraco.Web.PropertyEditors; using System.Reflection; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Services { @@ -2744,6 +2745,107 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Can_Get_Paged_Children_WithFilterAndOrder() + { + var languageService = ServiceContext.LocalizationService; + + var langUk = new Language("en-UK") { IsDefault = true }; + var langFr = new Language("fr-FR"); + var langDa = new Language("da-DK"); + + languageService.Save(langFr); + languageService.Save(langUk); + languageService.Save(langDa); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = contentTypeService.Get("umbTextpage"); + contentType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + var o = new[] { 2, 1, 3, 0, 4 }; // randomly different + for (var i = 0; i < 5; i++) + { + var contentA = new Content(null, -1, contentType); + contentA.SetCultureName("contentA" + i + "uk", langUk.IsoCode); + contentA.SetCultureName("contentA" + o[i] + "fr", langFr.IsoCode); + contentA.SetCultureName("contentX" + i + "da", langDa.IsoCode); + contentService.Save(contentA); + + var contentB = new Content(null, -1, contentType); + contentB.SetCultureName("contentB" + i + "uk", langUk.IsoCode); + contentB.SetCultureName("contentB" + o[i] + "fr", langFr.IsoCode); + contentB.SetCultureName("contentX" + i + "da", langDa.IsoCode); + contentService.Save(contentB); + } + + // get all + var list = contentService.GetPagedChildren(-1, 0, 100, out var total).ToList(); + + Console.WriteLine("ALL"); + WriteList(list); + + // 10 items (there's already a Home content in there...) + Assert.AreEqual(11, total); + Assert.AreEqual(11, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentX")), + Ordering.By("name", culture: langFr.IsoCode)).ToList(); + + Assert.AreEqual(0, total); + Assert.AreEqual(0, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentX")), + Ordering.By("name", culture: langDa.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME da:'contentX'"); + WriteList(list); + + Assert.AreEqual(10, total); + Assert.AreEqual(10, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentA")), + Ordering.By("name", culture: langFr.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME fr:'contentA', ORDER ASC"); + WriteList(list); + + Assert.AreEqual(5, total); + Assert.AreEqual(5, list.Count); + + for (var i = 0; i < 5; i++) + Assert.AreEqual("contentA" + i + "fr", list[i].GetCultureName(langFr.IsoCode)); + + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentA")), + Ordering.By("name", direction: Direction.Descending, culture: langFr.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME fr:'contentA', ORDER DESC"); + WriteList(list); + + Assert.AreEqual(5, total); + Assert.AreEqual(5, list.Count); + + for (var i = 0; i < 5; i++) + Assert.AreEqual("contentA" + (4-i) + "fr", list[i].GetCultureName(langFr.IsoCode)); + } + + private void WriteList(List list) + { + foreach (var content in list) + Console.WriteLine("[{0}] {1} {2} {3} {4}", content.Id, content.Name, content.GetCultureName("en-UK"), content.GetCultureName("fr-FR"), content.GetCultureName("da-DK")); + Console.WriteLine("-"); + } + [Test] public void Can_SaveRead_Variations() { diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 21f1ce82b2..6b52137542 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -64,6 +64,8 @@ namespace Umbraco.Tests.TestHelpers internal ScopeProvider ScopeProvider => Current.ScopeProvider as ScopeProvider; + protected ISqlContext SqlContext => Container.GetInstance(); + public override void SetUp() { base.SetUp(); From 2b83b44c9c48f7f8ec1a95502a46a24a77c0719c Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 18 Oct 2018 14:16:54 +0200 Subject: [PATCH 149/585] Fix ContentService.GetPagedChildren --- .../Persistence/NPocoSqlExtensions.cs | 11 ++-- .../Implement/ContentRepositoryBase.cs | 9 +-- .../Implement/DocumentRepository.cs | 55 ++++++++-------- .../Repositories/Implement/MediaRepository.cs | 9 ++- .../Implement/MemberRepository.cs | 12 ++-- .../Persistence/SqlContextExtensions.cs | 65 ++++++++++++++----- 6 files changed, 101 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index 7f6eb72a25..a5ab62d25f 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -73,7 +73,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias = null) { - var (s, a) = sql.SqlContext.Visit(predicate, alias); + var (s, a) = sql.SqlContext.VisitDto(predicate, alias); return sql.Where(s, a); } @@ -89,7 +89,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias1 = null, string alias2 = null) { - var (s, a) = sql.SqlContext.Visit(predicate, alias1, alias2); + var (s, a) = sql.SqlContext.VisitDto(predicate, alias1, alias2); return sql.Where(s, a); } @@ -321,9 +321,9 @@ namespace Umbraco.Core.Persistence /// Appends an ORDER BY DESC clause to the Sql statement. /// /// The Sql statement. - /// Expression specifying the fields. + /// Fields. /// The Sql statement. - public static Sql OrderByDescending(this Sql sql, params object[] fields) + public static Sql OrderByDescending(this Sql sql, params string[] fields) { return sql.Append("ORDER BY " + string.Join(", ", fields.Select(x => x + " DESC"))); } @@ -664,7 +664,7 @@ namespace Umbraco.Core.Persistence /// Adds columns to a SELECT Sql statement. /// /// The origin sql. - /// Expression indicating the column to select. + /// Columns to select. /// The Sql statement. public static Sql AndSelect(this Sql sql, params string[] fields) { @@ -688,7 +688,6 @@ namespace Umbraco.Core.Persistence return sql.Append(", " + string.Join(", ", sql.GetColumns(columnExpressions: fields))); } - /// /// Adds columns to a SELECT Sql statement. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 41f0f16225..34bc3713f3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -252,8 +252,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column // is empty for many nodes) - see: http://issues.umbraco.org/issue/U4-8831 - var dbfield = GetQuotedFieldName("umbracoNode", "id"); - (dbfield, _) = SqlContext.Visit(x => x.NodeId); // fixme?! + var (dbfield, _) = SqlContext.VisitDto(x => x.NodeId); if (ordering.IsCustomField || !ordering.OrderBy.InvariantEquals("id")) { psql.OrderBy(GetAliasedField(dbfield, sql)); // fixme why aliased? @@ -262,7 +261,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // create prepared sql // ensure it's single-line as NPoco PagingHelper has issues with multi-lines psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments); - // replace the magic culture parameter (see DocumentRepository.GetBaseQuery()) if (!ordering.Culture.IsNullOrWhiteSpace()) @@ -353,6 +351,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ordering.Culture.IsNullOrWhiteSpace()) return GetAliasedField(SqlSyntax.GetFieldName(x => x.Text), sql); + // "variantName" alias is defined in DocumentRepository.GetBaseQuery + // fixme - what if it is NOT a document but a ... media or whatever? + // previously, we inserted the join+select *here* so we were sure to have it, + // but now that's not the case anymore! return "variantName"; } @@ -433,7 +435,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // sort and filter sql = PreparePageSql(sql, filter, ordering); - // get a page of DTOs and the total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sql); totalRecords = Convert.ToInt32(pagedResult.TotalItems); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 3cb61ad8e1..df389c738a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -95,6 +95,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return GetBaseQuery(queryType, true); } + // gets the COALESCE expression for variant/invariant name + private string VariantNameSqlExpression + => SqlContext.VisitDto((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql; + protected virtual Sql GetBaseQuery(QueryType queryType, bool current) { var sql = SqlContext.Sql(); @@ -116,7 +120,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto)) .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))) - .AndSelect(SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS variantName"); + + // select the variant name, coalesce to the invariant name, as "variantName" + .AndSelect(VariantNameSqlExpression + " AS variantName"); break; } @@ -135,18 +141,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .LeftJoin(nested => nested.InnerJoin("pdv") .On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") - .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv"); + .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv") - //the magic [[[ISOCODE]]] will be replaced in ContentRepositoryBase.GetPage() by the current Iso code - sql + // left join on optional culture variation + //the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code .LeftJoin(nested => nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") - .On((version, ccv) => version.Id == ccv.VersionId, "pcv", "ccv"); + .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); sql .Where(x => x.NodeObjectType == NodeObjectTypeId); - // this would ensure we don't get the published version - keep for reference //sql // .WhereAny( @@ -157,7 +162,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (current) sql.Where(x => x.Current); // always get the current version - return sql; } @@ -246,7 +250,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + var content = (Content) entity; content.AddingEntity(); var publishing = content.PublishedState == PublishedState.Publishing; @@ -420,7 +424,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + var content = (Content) entity; // check if we need to make any database changes at all if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty()) @@ -707,30 +711,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { Sql filterSql = null; - // Here we create a default where clause from a temp IContent which will look in the contentVersion table for the content name - // if we are searching in a list view that contains variants, we want to look in the contentVersionCultureVariation table instead. - // The resulting clause will be used in the foreach below to compare against the original clause that comes from the "filter" and if they are the same - // we know that we are searching a list view and the proper where clause will be replaced to look in contentVersionCultureVariation table for the names. - var temp = Query().Where(x => x.Name.Contains("foo")); - var clause = temp.GetWhereClauses().First().Item1.Split(' ')[0]; - + // if we have a filter, map its clauses to an Sql statement if (filter != null) { + // if the clause works on "name", we need to swap the field and use the variantName instead, + // so that querying also works on variant content (for instance when searching a listview). + + // figure out how the "name" field is going to look like - so we can look for it + var nameField = SqlContext.VisitModelField(x => x.Name); + filterSql = Sql(); foreach (var filterClause in filter.GetWhereClauses()) { - // fixme - is this the right way of doing it??? + var clauseSql = filterClause.Item1; + var clauseArgs = filterClause.Item2; - // - var where = filterClause.Item1.Split(' ')[0] == clause - // normally, this would be the field alias (variantName) of the coalesce result between ContentVersionCulture and NodeDto names, however - // you can't refer to field alias in a WHERE clause so we have to put the coalesce calculation instead which refers to the original field - ? SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql - : filterClause.Item1; + // replace the name field + // we cannot reference an aliased field in a WHERE clause, so have to repeat the expression here + clauseSql = clauseSql.Replace(nameField, VariantNameSqlExpression); - filterSql.Append( - where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})", - filterClause.Item2); + // append the clause + filterSql.Append($"AND ({clauseSql})", clauseArgs); } } @@ -928,7 +929,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var cached = IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id) { - content[i] = (Content)cached; + content[i] = (Content) cached; continue; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 2390ce9a7b..dbfdc8e980 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -8,12 +8,12 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -100,7 +100,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Many: sql = sql.Select(r => r.Select(x => x.NodeDto) - .Select(x => x.ContentVersionDto)); + .Select(x => x.ContentVersionDto)) + + // ContentRepositoryBase expects a variantName field to order by name + // for now, just return the plain invariant node name + // fixme media should support variants !! + .AndSelect(x => Alias(x.Text, "variantName")); break; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 84ef154ae8..fd79b231de 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -6,12 +6,12 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -114,9 +114,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Single: case QueryType.Many: sql = sql.Select(r => - r.Select(x => x.ContentVersionDto) - .Select(x => x.ContentDto, r1 => - r1.Select(x => x.NodeDto))); + r.Select(x => x.ContentVersionDto) + .Select(x => x.ContentDto, r1 => + r1.Select(x => x.NodeDto))) + + // ContentRepositoryBase expects a variantName field to order by name + // so get it here, though for members it's just the plain node name + .AndSelect(x => Alias(x.Text, "variantName")); break; } diff --git a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs index e28816b6a4..249e2cafd0 100644 --- a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs @@ -17,11 +17,11 @@ namespace Umbraco.Core.Persistence /// An expression to visit. /// An optional table alias. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -33,11 +33,11 @@ namespace Umbraco.Core.Persistence /// An expression to visit. /// An optional table alias. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -50,11 +50,11 @@ namespace Umbraco.Core.Persistence /// An optional table alias for the first DTO. /// An optional table alias for the second DTO. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -68,11 +68,42 @@ namespace Umbraco.Core.Persistence /// An optional table alias for the first DTO. /// An optional table alias for the second DTO. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); + } + + /// + /// Visit a model expression. + /// + /// The type of the model. + /// An . + /// An expression to visit. + /// A SQL statement, and arguments, corresponding to the expression. + public static (string Sql, object[] Args) VisitModel(this ISqlContext sqlContext, Expression> expression) + { + var visitor = new ModelToSqlExpressionVisitor(sqlContext.SqlSyntax, sqlContext.Mappers); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); + } + + /// + /// Visit a model expression representing a field. + /// + /// The type of the model. + /// An . + /// An expression to visit, representing a field. + /// The name of the field. + public static string VisitModelField(this ISqlContext sqlContext, Expression> field) + { + var (sql, _) = sqlContext.VisitModel(field); + + // going to return " = @0" + // take the first part only + var pos = sql.IndexOf(' '); + return sql.Substring(0, pos); } } -} \ No newline at end of file +} From a12d736b6bb61e2d837562d93b06dc1a8e8aec3a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 14:56:00 +0200 Subject: [PATCH 150/585] show user in version dropdown --- .../common/infiniteeditors/rollback/rollback.controller.js | 5 ++++- .../src/views/common/infiniteeditors/rollback/rollback.html | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 2aa628d5ef..f96c1f6a3d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -79,7 +79,10 @@ return contentResource.getRollbackVersions(nodeId, culture) .then(function(data){ - vm.previousVersions = data; + vm.previousVersions = data.map(version => { + version.displayValue = version.versionDate + " - " + version.versionAuthorName; + return version; + }); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 8c6c1f6de5..5faba3a3ef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -38,7 +38,7 @@ From 09a744370140c36c5acbc578707100e504013550 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 15:16:37 +0200 Subject: [PATCH 151/585] dont show diff if a versionId is not set --- .../rollback/rollback.controller.js | 21 ++++++++++++------- .../content/umb-content-node-info.html | 3 ++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index f96c1f6a3d..63672879b0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -62,14 +62,21 @@ } function changeVersion(version) { - console.log("version", version); - contentResource.getRollbackVersion(version.versionId) - .then(function(data){ - vm.previousVersion = data; - vm.previousVersion.versionId = version.versionId; - createDiff(vm.currentVersion, vm.previousVersion); - }); + if(version && version.versionId) { + + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + contentResource.getRollbackVersion(version.versionId, culture) + .then(function(data){ + vm.previousVersion = data; + vm.previousVersion.versionId = version.versionId; + createDiff(vm.currentVersion, vm.previousVersion); + }); + + } else { + vm.diff = null; + } } function getVersions() { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 77a7161762..d8bc08491a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -49,7 +49,8 @@ button-style="outline" action="openRollback()" label-key="actions_rollback" - size="xs"> + size="xs" + add-ellipsis="true"> From 6f7d0c3a45f4025534731d805b2259460ac85393 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 14:24:32 +0100 Subject: [PATCH 152/585] Move logic out of controller into a Rollback method in the service level --- src/Umbraco.Core/Services/IContentService.cs | 13 +++ .../Services/Implement/ContentService.cs | 42 ++++++++++ src/Umbraco.Web/Editors/ContentController.cs | 83 +++++++------------ 3 files changed, 84 insertions(+), 54 deletions(-) diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 022bee8b41..3a0f920d48 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -461,5 +461,18 @@ namespace Umbraco.Core.Services IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0); #endregion + + #region Rollback + + /// + /// Rolls back the content to a specific version. + /// + /// The id of the content node + /// The version ID to rollback to + /// An optional culture - specifying a culture will only rollback the culture content + /// The user ID who is performing the rollback + OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0); + + #endregion } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index a849813b13..d7f11a3e2f 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -2490,5 +2490,47 @@ namespace Umbraco.Core.Services.Implement } #endregion + + #region Rollback + + public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + //Get the current copy of the node + var content = GetById(id); + + //Get the version + var version = GetVersion(versionId); + + //Good ole null checks + if (content == null || version == null) + { + return new OperationResult(OperationResultType.FailedCannot, evtMsgs); + } + + //Copy the changes from the version + content.CopyFrom(version, culture); + + //Save the content for the rollback + var rollbackSaveResult = Save(content, userId); + + //Depending on the save result - is what we log & audit along with what we return + if(rollbackSaveResult.Success == false) + { + //Log the error/warning + Logger.Error("User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + } + else + { + //Logging & Audit message + Logger.Error("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + Audit(AuditType.RollBack, $"Content '{content.Name}' was rolled back to version '{versionId}'", userId, id); + } + + return rollbackSaveResult; + } + + #endregion } } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b37bf82797..be4801ae61 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1707,9 +1707,8 @@ namespace Umbraco.Web.Editors { var rollbackVersions = new List(); - //Return a list of all versions of a specific content node - //First item is our current item/state (cant rollback to ourselves) - var versions = Services.ContentService.GetVersions(contentId).Skip(1); + //Return a list of all versions of a specific content node + var versions = Services.ContentService.GetVersions(contentId); //Not all nodes are variants & thus culture can be null //Only filter the collection @@ -1718,6 +1717,9 @@ namespace Umbraco.Web.Editors versions = versions.Where(x => x.UpdateDate == x.GetUpdateDate(culture)); } + //First item is our current item/state (cant rollback to ourselves) + versions = versions.Skip(1); + foreach (var version in versions) { var rollbackVersion = new RollbackVersion(); @@ -1744,68 +1746,41 @@ namespace Umbraco.Web.Editors { var version = Services.ContentService.GetVersion(versionId); var content = MapToDisplay(version); - - - //No culture set - so this is an invariant node - so just list me the first item in here - //TODO: Tripple check invariant nodes still has one item in the collection but with a language of null - if (culture == null) - { - return content.Variants.FirstOrDefault(); - } - - return content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); + + return culture == null + ? content.Variants.FirstOrDefault() //No culture set - so this is an invariant node - so just list me the first item in here + : content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); } [HttpPost] public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*") { - var userId = Security.GetUserId().ResultOr(0); + var rollbackResult = Services.ContentService.Rollback(contentId, versionId, culture, Security.GetUserId().ResultOr(0)); - Logger.Info("User ID {UserId} is attempting to rollback content '{ContentId}' to version '{VersionId}'", userId, contentId, versionId); - Services.AuditService.Add(AuditType.RollBack, "YO YO YO IM ROLLING BACK", userId, contentId); + if (rollbackResult.Success) + return Request.CreateResponse(HttpStatusCode.OK); - //Get the current copy of the node - var content = Services.ContentService.GetById(contentId); + var notificationModel = new SimpleNotificationModel(); - //Get the version - var version = Services.ContentService.GetVersion(versionId); - - //Copy the changes from the version - content.CopyFrom(version, culture); - - //Save the update - var saveResult = Services.ContentService.Save(content, userId); - if(saveResult.Success == false) + switch (rollbackResult.Result) { - Logger.Error("Unable to rollback content '{ContentId}' to version '{VersionId}'", contentId, versionId); - - var notificationModel = new SimpleNotificationModel(); - - switch (saveResult.Result) - { - case OperationResultType.Failed: - case OperationResultType.FailedCannot: - case OperationResultType.FailedExceptionThrown: - case OperationResultType.NoOperation: - default: - notificationModel.AddErrorNotification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - null); //TODO: There is no specific failed to save error message AFAIK - break; - case OperationResultType.FailedCancelledByEvent: - notificationModel.AddErrorNotification( - Services.TextService.Localize("speechBubbles/operationCancelledHeader"), - Services.TextService.Localize("speechBubbles/operationCancelledText")); - break; - } - - return Request.CreateValidationErrorResponse(notificationModel); + case OperationResultType.Failed: + case OperationResultType.FailedCannot: + case OperationResultType.FailedExceptionThrown: + case OperationResultType.NoOperation: + default: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + null); //TODO: There is no specific failed to save error message AFAIK + break; + case OperationResultType.FailedCancelledByEvent: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationCancelledHeader"), + Services.TextService.Localize("speechBubbles/operationCancelledText")); + break; } - - Logger.Info("User ID {UserId} rolled back content '{ContentId}' to version '{VersionId}'", userId, contentId, versionId); - //return ok - return Request.CreateResponse(HttpStatusCode.OK); + return Request.CreateValidationErrorResponse(notificationModel); } } } From 0f528a7a8903cdc744ac28b81621e6fe70f66c8e Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 15:25:17 +0200 Subject: [PATCH 153/585] disable roll back button when no version is selected --- .../common/infiniteeditors/rollback/rollback.controller.js | 3 +++ .../src/views/common/infiniteeditors/rollback/rollback.html | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 63672879b0..eac4fab8a7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -19,6 +19,7 @@ vm.variantVersions = []; vm.diff = null; vm.currentVersion = null; + vm.rollbackButtonDisabled = true; // find the current version for invariant nodes if($scope.model.node.variants.length === 1) { @@ -72,10 +73,12 @@ vm.previousVersion = data; vm.previousVersion.versionId = version.versionId; createDiff(vm.currentVersion, vm.previousVersion); + vm.rollbackButtonDisabled = false; }); } else { vm.diff = null; + vm.rollbackButtonDisabled = true; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 5faba3a3ef..6758f1df9b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -93,6 +93,7 @@ button-style="success" state="vm.rollbackButtonState" label-key="actions_rollback" + disabled="vm.rollbackButtonDisabled" action="vm.rollback()"> From 321313f134e34df9956994707ebcc4923bacf74b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 14:32:44 +0100 Subject: [PATCH 154/585] Remove rollback .aspx dialog --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 - .../Umbraco/dialogs/rollBack.aspx | 98 ------------------- 2 files changed, 99 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index de52021220..d18dab1987 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -348,7 +348,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx deleted file mode 100644 index 84b2cc5cb8..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx +++ /dev/null @@ -1,98 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master"AutoEventWireup="True" Inherits="umbraco.presentation.dialogs.rollBack" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - - -
- - - - - - - - () - - - - - - - - Diff - Html - - - - - - -
-
-

- -

-
- - - -
-
-
-
- - -
From 1efe90ba1d362f7be6766f8c764f06f5c22c44e6 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 14:54:29 +0100 Subject: [PATCH 155/585] More rollback .aspx dialogs removed --- src/Umbraco.Web/Umbraco.Web.csproj | 9 - .../umbraco/dialogs/rollBack.aspx | 99 ----------- .../umbraco/dialogs/rollBack.aspx.cs | 156 ------------------ .../umbraco/dialogs/rollBack.aspx.designer.cs | 150 ----------------- 4 files changed, 414 deletions(-) delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.designer.cs diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5235728814..d87ae5dd1c 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1312,12 +1312,6 @@ editPackage.aspx - - rollBack.aspx - - - rollBack.aspx - @@ -1402,9 +1396,6 @@ ASPXCodeBehind - - ASPXCodeBehind - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx deleted file mode 100644 index 88f106d269..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx +++ /dev/null @@ -1,99 +0,0 @@ -<%@ Page Language="c#" CodeBehind="rollBack.aspx.cs" MasterPageFile="../masterpages/umbracoDialog.Master" AutoEventWireup="True" Inherits="umbraco.presentation.dialogs.rollBack" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" %> - - - - - - - - -
- - - - - - - - () - - - - - - - - Diff - Html - - - - - - -
-
-

- -

-
- - - -
-
-
-
- - -
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs deleted file mode 100644 index 54f2871149..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs +++ /dev/null @@ -1,156 +0,0 @@ -//TODO: REbuild this in angular and then remove - -//using System; -//using System.Collections; -//using System.ComponentModel; -//using System.Data; -//using System.Drawing; -//using System.Linq; -//using System.Web; -//using System.Web.SessionState; -//using System.Web.UI; -//using System.Web.UI.WebControls; -//using System.Web.UI.HtmlControls; -// -//using umbraco.cms.businesslogic.web; -//using umbraco.cms.businesslogic.property; - -//namespace umbraco.presentation.dialogs -//{ -// /// -// /// Summary description for rollBack. -// /// -// public partial class rollBack : UmbracoEnsuredPage -// { -// public rollBack() -// { -// CurrentApp = Constants.Applications.Content.ToString(); - -// } -// private Document currentDoc = new Document(int.Parse(helper.Request("nodeId"))); - -// protected void version_load(object sender, EventArgs e) { - -// if (allVersions.SelectedValue != "") -// { -// diffPanel.Visible = true; -// Document rollback = new Document(currentDoc.Id, new Guid(allVersions.SelectedValue)); - -// propertiesCompare.Text = "" + Services.TextService.Localize("general/name") + ":" + rollback.Text + ""; -// propertiesCompare.Text += "" + Services.TextService.Localize("content/createDate") + ":" + rollback.VersionDate.ToLongDateString() + " " + rollback.VersionDate.ToLongTimeString() + " " + Services.TextService.Localize("general/by") + ": " + rollback.User.Name + ""; - -// if (rbl_mode.SelectedValue == "diff") -// lt_notice.Text = Services.TextService.Localize("rollback/diffHelp"); -// else -// lt_notice.Text = Services.TextService.Localize("rollback/htmlHelp"); - - -// var props = rollback.GenericProperties; -// foreach (Property p in props) -// { -// try -// { - -// if (p.Value != null) -// { - -// //new property value... -// string thevalue = p.Value.ToString(); - -// if (rbl_mode.SelectedValue == "diff") -// { - -// //if display mode is set to diff... -// thevalue = library.StripHtml(p.Value.ToString()); -// Property cP = currentDoc.getProperty(p.PropertyType); -// if (cP != null && cP.Value != null) -// { - -// string cThevalue = library.StripHtml(cP.Value.ToString()); - -// propertiesCompare.Text += "" + p.PropertyType.Name + ":" + library.ReplaceLineBreaks(cms.businesslogic.utilities.Diff.Diff2Html(cThevalue, thevalue)) + ""; - - -// } -// else -// { -// //If no current version of the value... display with no diff. -// propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; -// } - - -// } -// else -// { -// //If display mode is html -// propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; -// } - -// //previewVersionContent.Controls.Add(new LiteralControl("

" + p.PropertyType.Name + "
")); -// //previewVersionContent.Controls.Add(new LiteralControl(thevalue)); - -// } -// //previewVersionContent.Controls.Add(new LiteralControl("

")); -// } -// catch { } -// } - -// Button1.Visible = true; - - -// } -// else -// { -// diffPanel.Visible = false; -// Button1.Visible = false; -// } - -// } - -// protected void Page_Load(object sender, System.EventArgs e) -// { - -// if (String.IsNullOrEmpty(allVersions.SelectedValue)) -// rbl_mode.AutoPostBack = false; -// else -// rbl_mode.AutoPostBack = true; - -// currentVersionTitle.Text = currentDoc.Text; -// currentVersionMeta.Text = Services.TextService.Localize("content/createDate") + ": " + currentDoc.VersionDate.ToShortDateString() + " " + currentDoc.VersionDate.ToShortTimeString(); - -// if (!IsPostBack) { -// allVersions.Items.Add(new ListItem(Services.TextService.Localize("rollback/selectVersion")+ "...", "")); - -// foreach (DocumentVersionList dl in currentDoc.GetVersions()) -// { -// //we don't need to show the current version -// if (dl.Version == currentDoc.Version) -// continue; -// -// allVersions.Items.Add(new ListItem(dl.Text + " (" + Services.TextService.Localize("content/createDate") + ": " + dl.Date.ToShortDateString() + " " + dl.Date.ToShortTimeString() + ")", dl.Version.ToString())); -// } -// Button1.Text = Services.TextService.Localize("actions/rollback"); -// } -// } -// protected void doRollback_Click(object sender, System.EventArgs e) -// { -// if (allVersions.SelectedValue.Trim() != "") -// { -// Document d = new Document(int.Parse(helper.Request("nodeId"))); -// d.RollBack(new Guid(allVersions.SelectedValue), Security.CurrentUser); - -// BusinessLogic.Log.Add(BusinessLogic.LogTypes.RollBack, Security.CurrentUser, d.Id, "Version rolled back to revision '" + allVersions.SelectedValue + "'"); - -// Document rollback = new Document(d.Id, new Guid(allVersions.SelectedValue)); -// feedBackMsg.type = global::Umbraco.Web._Legacy.Controls.Feedback.feedbacktype.success; -// string[] vars = {rollback.Text, rollback.VersionDate.ToLongDateString()}; - -// feedBackMsg.Text = ui.Text("rollback", "documentRolledBack", vars, new global::umbraco.BusinessLogic.User(0)) + "

" + Services.TextService.Localize("closeThisWindow") + ""; -// diffPanel.Visible = false; -// pl_buttons.Visible = false; -// -// ClientTools.ReloadLocationIfMatched(string.Format("/content/content/edit/{0}", d.Id)); -// } -// } -// } -//} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.designer.cs deleted file mode 100644 index ab313afa40..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.designer.cs +++ /dev/null @@ -1,150 +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.presentation.dialogs { - - - public partial class rollBack { - - ///

- /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// feedBackMsg control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Feedback feedBackMsg; - - /// - /// pp_selectVersion control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pp_selectVersion; - - /// - /// pp_currentVersion control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_currentVersion; - - /// - /// currentVersionTitle control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal currentVersionTitle; - - /// - /// currentVersionMeta control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal currentVersionMeta; - - /// - /// pp_rollBackTo control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_rollBackTo; - - /// - /// allVersions control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.DropDownList allVersions; - - /// - /// pp_view control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_view; - - /// - /// rbl_mode control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RadioButtonList rbl_mode; - - /// - /// diffPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel diffPanel; - - /// - /// lt_notice control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal lt_notice; - - /// - /// propertiesCompare control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal propertiesCompare; - - /// - /// pl_buttons control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlGenericControl pl_buttons; - - /// - /// Button1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Button Button1; - } -} From 6850599ce99aa4b68de4e5a43790b01b1318d51f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 15:04:40 +0100 Subject: [PATCH 156/585] Remove the emtpy folders from CSProj in the legacy umbraco.presentation folder --- src/Umbraco.Web/Umbraco.Web.csproj | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d87ae5dd1c..63e497a1c4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1449,10 +1449,7 @@ umbraco_org_umbraco_update_CheckForUpgrade
- - - - + From 0f2381a94da17302deac528578dee25b8803fcf5 Mon Sep 17 00:00:00 2001 From: Jamie Townsend Date: Sun, 14 Oct 2018 15:35:41 +0100 Subject: [PATCH 183/585] Merge multiple ng-if attributes --- .../src/views/users/views/user/details.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index d95e7cb80f..8d7cba5a78 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -262,13 +262,12 @@
- From f37f679031045fadeb28f8988d88a496af74cd33 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 19 Oct 2018 15:19:37 +0100 Subject: [PATCH 184/585] Adds ContentApps to the Composition extension methods --- src/Umbraco.Web/CompositionExtensions.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/CompositionExtensions.cs index 332380009d..f33c8e98a8 100644 --- a/src/Umbraco.Web/CompositionExtensions.cs +++ b/src/Umbraco.Web/CompositionExtensions.cs @@ -10,6 +10,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.ContentApps; // the namespace here is intentional - although defined in Umbraco.Web assembly, // this class should be visible when using Umbraco.Core.Components, alongside @@ -32,7 +33,15 @@ namespace Umbraco.Core.Components /// internal static ActionCollectionBuilder Actions(this Composition composition) => composition.Container.GetInstance(); - + + /// + /// Gets the content apps collection builder. + /// + /// The composition. + /// + public static ContentAppDefinitionCollectionBuilder ContentApps(this Composition composition) + => composition.Container.GetInstance(); + /// /// Gets the content finders collection builder. /// From 6996a06d64b270268267d655760e057fa82bfcd4 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Thu, 11 Oct 2018 19:46:43 +0200 Subject: [PATCH 185/585] Only add "Move" action once --- .../Trees/ContentTypeTreeController.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 33ae50dbbf..1544e5d2d0 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -117,21 +117,11 @@ namespace Umbraco.Web.Trees if (enableInheritedDocumentTypes) { menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - - //no move action if this is a child doc type - if (parent == null) - { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); - } } - else + //no move action if this is a child doc type + if (parent == null) { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias))); - //no move action if this is a child doc type - if (parent == null) - { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); - } + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); } menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionCopy.Instance.Alias))); menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionExport.Instance.Alias)), true).ConvertLegacyMenuItem(new UmbracoEntity From 5dc67deeecde39a5f3fe5b6e70f485d45b5ca89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Kottal?= Date: Wed, 17 Oct 2018 11:22:55 +0200 Subject: [PATCH 186/585] adds the right description to UmbracoHelper.Concatenate --- src/Umbraco.Web/UmbracoHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index b06b786104..bb9bd2d50e 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1411,7 +1411,7 @@ namespace Umbraco.Web } /// - /// Will take the first non-null value in the collection and return the value of it. + /// Joins any number of int/string/objects into one string /// public string Concatenate(params object[] args) { From 7071b21db7d2251f03855d56f4c857eeed24d642 Mon Sep 17 00:00:00 2001 From: Carole Rennie Logan Date: Tue, 16 Oct 2018 19:50:11 +0100 Subject: [PATCH 187/585] Updating example content app manifest Including roles --- src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 3d4b24d359..d5f6c2b8c4 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -20,7 +20,8 @@ namespace Umbraco.Core.Manifest // show: [ // optional, default is always show // '-content/foo', // hide for content type 'foo' // '+content/*', // show for all other content types - // '+media/*' // show for all media types + // '+media/*', // show for all media types + // '+role/admin' // show for admin users. Role based permissions will override others. // ] // }, // ... From ef343f04a8e847a128e965733b34523819ab412d Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Thu, 11 Oct 2018 20:19:49 +0200 Subject: [PATCH 188/585] Only hide icons if hideIcons is set to 'true' --- .../src/views/components/buttons/umb-toggle.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html index 670b6c64f2..bc5c114bb6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html @@ -6,9 +6,9 @@
- +
- +
From 00cf8dcf05393e15f8a743763d8f9059f2a6979f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 11 Oct 2018 14:28:22 +0200 Subject: [PATCH 189/585] Reload the node when publishing all unpublished children --- .../lib/umbraco/LegacyUmbClientMgr.js | 3 ++- .../umbraco_client/Dialogs/PublishDialog.js | 13 ++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js index 9fe887d0b6..223695322f 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js @@ -150,7 +150,8 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); sourceUrl: currentMenuNode.childNodesUrl, updateDefinition: function() { throw "'updateDefinition' method is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; - } + }, + expanded: currentMenuNode.expanded === true }; //defined getters that will throw a not implemented/supported exception Object.defineProperty(legacyNode, "menu", { diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js b/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js index d034a07a4a..1781f71b27 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js @@ -37,12 +37,13 @@ }, startPublish: function() { this.processStatus("publishing"); - + + var includeUnpublished = self._koViewModel.includeUnpublished(); $.post(self._opts.restServiceLocation + "PublishDocument", JSON.stringify({ documentId: self._opts.documentId, publishDescendants: self._koViewModel.publishAll(), - includeUnpublished: self._koViewModel.includeUnpublished() + includeUnpublished: includeUnpublished }), function (e) { self._koViewModel.processStatus("complete"); @@ -59,7 +60,13 @@ //sync the tree UmbClientMgr.mainTree().setActiveTreeType('content'); - UmbClientMgr.mainTree().syncTree(self._opts.documentPath, true); + UmbClientMgr.mainTree().syncTree(self._opts.documentPath, true) + if (includeUnpublished) { + var node = UmbClientMgr.mainTree().getActionNode(); + if (node.expanded === true) { + UmbClientMgr.mainTree().reloadActionNode(); + } + } }); } }; From 4ef9c3f086d05f39f23f89070e10e366902e3d74 Mon Sep 17 00:00:00 2001 From: Chris Houston Date: Thu, 18 Oct 2018 00:58:02 -0400 Subject: [PATCH 190/585] When typing to configure some of the data types on a small width screen ( mobile or very narrow browser window ) the editing controls for some of the data types did not wrap underneath the titles and in some cases they disappeared completely. This adds a 100% width on small screen sizes and then reverts back to the original 60% width after 800px's. --- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 7 ++++++- .../src/views/components/property/umb-property-editor.html | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 43f5aa5a9a..198885e6be 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -2,7 +2,12 @@ // Container styles // -------------------------------------------------- .umb-property-editor { - min-width:66.6%; + @media (max-width: 800px) { + width: 100%; + } + @media (min-width: 800px) { + min-width:66.6%; + } &-pull { float:left; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html index bbf70f7a8e..a22a15e808 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html @@ -1,3 +1,3 @@ -
+
From 5efb079f5623cfafcd251498b013076bc60ba44d Mon Sep 17 00:00:00 2001 From: sebastien-sougnez Date: Thu, 18 Oct 2018 17:18:53 +0200 Subject: [PATCH 191/585] Changed font-awesome verison from 4.2 to 4.7 --- src/Umbraco.Web.UI.Client/bower.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 80d7d9366b..5f94ecf629 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -31,7 +31,7 @@ "moment": "~2.10.3", "ace-builds": "^1.2.3", "clipboard": "1.7.1", - "font-awesome": "~4.2" + "font-awesome": "~4.7" }, "install": { "path": "lib-bower", @@ -43,11 +43,11 @@ ], "sources": { "moment": [ - "bower_components/moment/min/moment.min.js", - "bower_components/moment/min/moment-with-locales.js", - "bower_components/moment/min/moment-with-locales.min.js", - "bower_components/moment/locale/*.js" - ], + "bower_components/moment/min/moment.min.js", + "bower_components/moment/min/moment-with-locales.js", + "bower_components/moment/min/moment-with-locales.min.js", + "bower_components/moment/locale/*.js" + ], "underscore": [ "bower_components/underscore/underscore-min.js", "bower_components/underscore/underscore-min.map" From f4a9617487fee6043966aa8510855e661ddca568 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Thu, 18 Oct 2018 19:35:59 +0200 Subject: [PATCH 192/585] Improved the description for the "Icon" property --- src/Umbraco.Core/Models/IContentTypeBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 80e62f50cf..f1e10b03a2 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -21,7 +21,12 @@ namespace Umbraco.Core.Models string Description { get; set; } /// - /// Gets or Sets the Icon for the ContentType + /// Gets or sets the icon for the content type. The value is a CSS class name representing + /// the icon (eg. icon-home) along with an optional CSS class name representing the + /// color (eg. icon-blue). Put together, the value for this scenario would be + /// icon-home color-blue. + /// + /// If a class name for the color isn't specified, the icon color will default to black. /// string Icon { get; set; } @@ -119,4 +124,4 @@ namespace Umbraco.Core.Models /// bool MovePropertyType(string propertyTypeAlias, string propertyGroupName); } -} \ No newline at end of file +} From 5bd87efb3fcc20e1821f119629651f222f78da9c Mon Sep 17 00:00:00 2001 From: Pawel Bres Date: Thu, 18 Oct 2018 13:37:59 +0200 Subject: [PATCH 193/585] Updated URL to the Contributing page of v7 The old link was not working anymore and was pointing to 404 GitHub page --- .github/V8_GETTING_STARTED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md index def923e0d0..62b376b0e7 100644 --- a/.github/V8_GETTING_STARTED.md +++ b/.github/V8_GETTING_STARTED.md @@ -23,7 +23,7 @@ We recommend running the site with the Visual Studio since you'll be able to rem ### Making code changes -* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/docs/CONTRIBUTING.md)_ +* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md)_ * Any .NET changes you make you just need to compile * Any Angular/JS changes you make you will need to make sure you are running the Gulp build. Easiest way to do this is from within Visual Studio in the `Task Runner Explorer`. You can find this window by pressing `ctrl + q` and typing in `Task Runner Explorer`. In this window you'll see all Gulp tasks, double click on the `dev` task, this will compile the angular solution and start a file watcher, then any html/js changes you make are automatically built. * When making js changes, you should have the chrome developer tools open to ensure that cache is disabled From e1919e0c5253df9a6aa94df0df8a811499c80f35 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sun, 21 Oct 2018 13:54:17 +0200 Subject: [PATCH 194/585] Added keys for "enterAlias" and "generatingAlias" --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f1b415f3c5..ff234c71da 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -464,6 +464,8 @@ Enter a message... Your username is usually your email #value or ?key=value + Enter alias... + Generating alias... Allow at root From 6f359b3d18d3231dd79a74358273605c2144336c Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sun, 21 Oct 2018 13:57:01 +0200 Subject: [PATCH 195/585] Added keys for "enterAlias" and "generatingAlias" --- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 ++ 1 file changed, 2 insertions(+) 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 31d157f54c..ed3a261979 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -464,6 +464,8 @@ Enter a message... Your username is usually your email #value or ?key=value + Enter alias... + Generating alias... Allow at root From 39edf4c6dd4dd0fc5c2b530f3b6577aa959ba44c Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sun, 21 Oct 2018 13:58:45 +0200 Subject: [PATCH 196/585] Added keys for "enterAlias" and "generatingAlias" --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index e68365ddb8..886662d17f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -443,6 +443,8 @@ Indtast din e-mail Indtast en besked... Dit brugernavn er typisk din e-mailadresse + Indtast alias... + Genererer alias... Tillad på rodniveau From 827bc19f6a4db824acf4af5161a6b234bcd7bc0a Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sun, 21 Oct 2018 14:03:22 +0200 Subject: [PATCH 197/585] Localized umb-generate-alias placeholder texts --- .../components/umbGenerateAlias.directive.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js index 66e93d70d8..b3accc18b4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js @@ -48,7 +48,7 @@ the directive will use {@link umbraco.directives.directive:umbLockedField umbLoc **/ angular.module("umbraco.directives") - .directive('umbGenerateAlias', function ($timeout, entityResource) { + .directive('umbGenerateAlias', function ($timeout, entityResource, localizationService) { return { restrict: 'E', templateUrl: 'views/components/umb-generate-alias.html', @@ -67,7 +67,21 @@ angular.module("umbraco.directives") var updateAlias = false; scope.locked = true; - scope.placeholderText = "Enter alias..."; + + scope.labels = { + idle: "Enter alias...", + busy: "Generating alias...", + }; + + scope.placeholderText = scope.labels.idle; + + localizationService.localize('placeholders_enterAlias').then(function (value) { + scope.labels.idle = scope.placeholderText = value; + }); + + localizationService.localize('placeholders_generatingAlias').then(function (value) { + scope.labels.busy = value; + }); function generateAlias(value) { @@ -78,7 +92,7 @@ angular.module("umbraco.directives") if( value !== undefined && value !== "" && value !== null) { scope.alias = ""; - scope.placeholderText = "Generating Alias..."; + scope.placeholderText = scope.labels.busy; generateAliasTimeout = $timeout(function () { updateAlias = true; @@ -92,7 +106,7 @@ angular.module("umbraco.directives") } else { updateAlias = true; scope.alias = ""; - scope.placeholderText = "Enter alias..."; + scope.placeholderText = scope.labels.idle; } } From 90416a4d879e93f2c7dc69532597a6cb67c10b3f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 12 Oct 2018 08:34:33 +0200 Subject: [PATCH 198/585] Add a custom content item binder for saving blueprints (fixes #2985) --- src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../WebApi/Binders/BlueprintItemBinder.cs | 28 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 6268759e29..4643cb2a1e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -576,7 +576,7 @@ namespace Umbraco.Web.Editors [FileUploadCleanupFilter] [ContentPostValidate] public ContentItemDisplay PostSaveBlueprint( - [ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) + [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) { var contentItemDisplay = PostSaveInternal(contentItem, content => diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 66cce9b1bf..e021de0011 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -823,6 +823,7 @@ + diff --git a/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs new file mode 100644 index 0000000000..825c5b01c3 --- /dev/null +++ b/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs @@ -0,0 +1,28 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.WebApi.Binders +{ + internal class BlueprintItemBinder : ContentItemBinder + { + public BlueprintItemBinder(ApplicationContext applicationContext) + : base(applicationContext) + { + } + + /// + /// Constructor + /// + public BlueprintItemBinder() + : this(ApplicationContext.Current) + { + } + + protected override IContent GetExisting(ContentItemSave model) + { + return ApplicationContext.Services.ContentService.GetBlueprintById(Convert.ToInt32(model.Id)); + } + } +} \ No newline at end of file From 5da5df5bf9e9584d1675e38219bcd4f5f399f304 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 16 Oct 2018 13:25:44 +0200 Subject: [PATCH 199/585] Hide "reset password" on the login screen if system emails can't be sent. --- .../src/views/common/dialogs/login.controller.js | 2 +- src/Umbraco.Web/Editors/BackOfficeServerVariables.cs | 3 ++- 2 files changed, 3 insertions(+), 2 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 9b703a0987..771718ac11 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 @@ -207,7 +207,7 @@ } } - $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; $scope.showLogin = function () { $scope.errorMsg = ""; diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 0444820bc4..6e27a8f8e5 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Editors var keepOnlyKeys = new Dictionary { {"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl"}}, - {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage"}}, + {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage", "canSendRequiredEmail"}}, {"application", new[] {"applicationPath", "cacheBuster"}}, {"isDebuggingEnabled", new string[] { }}, {"features", new [] {"disabledFeatures"}} @@ -311,6 +311,7 @@ namespace Umbraco.Web.Editors {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, {"showUserInvite", EmailSender.CanSendRequiredEmail}, + {"canSendRequiredEmail", EmailSender.CanSendRequiredEmail}, } }, { From 01d9785097ec0dd9d55e274866f314a4af034b17 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Sun, 21 Oct 2018 16:00:11 +0200 Subject: [PATCH 200/585] 3364 - Suggestion: Make use of confirm action directive on repeatable textstring when deleting (#3365) --- .../less/components/umb-multiple-textbox.less | 17 ++++++++++++-- .../multipletextbox.controller.js | 22 ++++++++++++++++++- .../multipletextbox/multipletextbox.html | 19 +++++++++++----- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less index 21f59a3e2d..52cc7a9aaf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less @@ -1,4 +1,17 @@ -.umb-multiple-textbox .textbox-wrapper { +.umb-multiple-textbox{ + &__confirm{ + position: relative; + + &-action{ + margin: 0; + padding: 2px; + background: transparent; + border: 0 none; + } + } +} + +.umb-multiple-textbox .textbox-wrapper { align-items: center; margin-bottom: 15px; } @@ -7,7 +20,7 @@ margin-bottom: 0; } -.umb-multiple-textbox .textbox-wrapper i { +.umb-multiple-textbox .textbox-wrapper i:not(.icon-delete, .icon-check) { margin-right: 5px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js index acee0f5ce9..9f190aab41 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js @@ -2,6 +2,9 @@ var backspaceHits = 0; + // Set the visible prompt to -1 to ensure it will not be visible + $scope.promptIsVisible = "-1"; + $scope.sortableOptions = { axis: 'y', containment: 'parent', @@ -89,6 +92,9 @@ }; $scope.remove = function (index) { + // Make sure not to trigger other prompts when remove is triggered + $scope.hidePrompt(); + var remainder = []; for (var x = 0; x < $scope.model.value.length; x++) { if (x !== index) { @@ -98,6 +104,20 @@ $scope.model.value = remainder; }; + $scope.showPrompt = function (idx, item){ + + var i = $scope.model.value.indexOf(item); + + // Make the prompt visible for the clicked tag only + if (i === idx) { + $scope.promptIsVisible = i; + } + } + + $scope.hidePrompt = function(){ + $scope.promptIsVisible = "-1"; + } + } -angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html index 123385d681..db98225e5f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html @@ -5,11 +5,20 @@ - - - + +
+ + + + +
+
Date: Sat, 20 Oct 2018 12:16:24 +0200 Subject: [PATCH 201/585] Border color for .add-on should be @purple-l3 --- src/Umbraco.Web.UI.Client/src/less/forms.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 5926ea2163..bbedfe1a0b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -594,7 +594,7 @@ div.help { text-align: center; text-shadow: 0 1px 0 @white; background-color: @gray-10; - border: 1px solid @gray-8; + border: 1px solid @purple-l3; } .add-on, .btn, From 1de7b8f10fecdb37b45aab60cdc0ae3d0eff1b78 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sat, 20 Oct 2018 12:26:16 +0200 Subject: [PATCH 202/585]
 elements should at least have white-space:
 pre-wrap

---
 src/Umbraco.Web.UI.Client/src/less/hacks.less | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less
index 18439b554c..6505dc6e62 100644
--- a/src/Umbraco.Web.UI.Client/src/less/hacks.less
+++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less
@@ -202,7 +202,7 @@ pre {
   //font-size: @baseFontSize - 1; // 14px to 13px
   color: @gray-2;
   line-height: @baseLineHeight;
-  white-space: pre-line; // 1
+  white-space: pre-wrap; // 1
   overflow-x: auto; // 1
   background-color: @gray-10;
   border: 1px solid @gray-8;
@@ -222,4 +222,4 @@ pre {
     background-color: transparent;
     border: 0;
   }
-}
\ No newline at end of file
+}

From c401782d5e21bef81177789af7986e2652a46ef5 Mon Sep 17 00:00:00 2001
From: Chris Houston 
Date: Sun, 21 Oct 2018 07:27:39 -0700
Subject: [PATCH 203/585] Added clean-css to the client side build process
 (#3371)

---
 src/Umbraco.Web.UI.Client/gulpfile.js       |   9 +-
 src/Umbraco.Web.UI.Client/package-lock.json | 204 +++++++++++---------
 src/Umbraco.Web.UI.Client/package.json      |   1 +
 3 files changed, 119 insertions(+), 95 deletions(-)

diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js
index 758f538656..d1bbfe65db 100644
--- a/src/Umbraco.Web.UI.Client/gulpfile.js
+++ b/src/Umbraco.Web.UI.Client/gulpfile.js
@@ -6,21 +6,22 @@ var wrap = require("gulp-wrap-js");
 var sort = require('gulp-sort');
 var connect = require('gulp-connect');
 var open = require('gulp-open');
-const babel = require("gulp-babel");
+var babel = require("gulp-babel");
 var runSequence = require('run-sequence');
-const imagemin = require('gulp-imagemin');
+var imagemin = require('gulp-imagemin');
 
 var _ = require('lodash');
 var MergeStream = require('merge-stream');
 
 // js
-const eslint = require('gulp-eslint');
+var eslint = require('gulp-eslint');
 
 //Less + css
 var postcss = require('gulp-postcss');
 var less = require('gulp-less');
 var autoprefixer = require('autoprefixer');
 var cssnano = require('cssnano');
+var cleanCss = require("gulp-clean-css");
 
 // Documentation
 var gulpDocs = require('gulp-ngdocs');
@@ -49,7 +50,6 @@ function processJs(files, out) {
 }
 
 function processLess(files, out) {
-
     var processors = [
          autoprefixer,
          cssnano({zindex: false})
@@ -57,6 +57,7 @@ function processLess(files, out) {
 
     return gulp.src(files)
         .pipe(less())
+        .pipe(cleanCss())
         .pipe(postcss(processors))
         .pipe(rename(out))
         .pipe(gulp.dest(root + targets.css));
diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json
index 4b8cc3e6c2..deda717214 100644
--- a/src/Umbraco.Web.UI.Client/package-lock.json
+++ b/src/Umbraco.Web.UI.Client/package-lock.json
@@ -1266,7 +1266,7 @@
     "array-slice": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz",
-      "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=",
+      "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==",
       "dev": true
     },
     "array-union": {
@@ -1440,7 +1440,7 @@
     "base": {
       "version": "0.11.2",
       "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
-      "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=",
+      "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
       "dev": true,
       "requires": {
         "cache-base": "^1.0.1",
@@ -1464,7 +1464,7 @@
         "is-accessor-descriptor": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
-          "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
           "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
@@ -1473,7 +1473,7 @@
         "is-data-descriptor": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
-          "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
           "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
@@ -1482,7 +1482,7 @@
         "is-descriptor": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
-          "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
           "dev": true,
           "requires": {
             "is-accessor-descriptor": "^1.0.0",
@@ -1680,7 +1680,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -1696,7 +1696,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -1822,7 +1822,7 @@
     "braces": {
       "version": "2.3.2",
       "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
-      "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=",
+      "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
       "dev": true,
       "requires": {
         "arr-flatten": "^1.1.0",
@@ -1948,7 +1948,7 @@
         },
         "uuid": {
           "version": "2.0.3",
-          "resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
           "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
           "dev": true
         },
@@ -2014,7 +2014,7 @@
     "cache-base": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
-      "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=",
+      "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
       "dev": true,
       "requires": {
         "collection-visit": "^1.0.0",
@@ -2225,7 +2225,7 @@
     "class-utils": {
       "version": "0.3.6",
       "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
-      "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=",
+      "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
       "dev": true,
       "requires": {
         "arr-union": "^3.1.0",
@@ -2245,6 +2245,23 @@
         }
       }
     },
+    "clean-css": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
+      "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
+      "dev": true,
+      "requires": {
+        "source-map": "~0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
     "cli-cursor": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
@@ -2458,7 +2475,7 @@
     },
     "commander": {
       "version": "2.8.1",
-      "resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
       "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
       "dev": true,
       "requires": {
@@ -2700,7 +2717,7 @@
     "content-type": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
-      "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
       "dev": true
     },
     "convert-source-map": {
@@ -3114,7 +3131,7 @@
             },
             "readable-stream": {
               "version": "1.0.34",
-              "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
               "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
               "dev": true,
               "requires": {
@@ -3211,7 +3228,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -3227,7 +3244,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -3318,7 +3335,7 @@
         },
         "readable-stream": {
           "version": "1.0.34",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
           "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
           "dev": true,
           "requires": {
@@ -3379,7 +3396,7 @@
         },
         "readable-stream": {
           "version": "1.0.34",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
           "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
           "dev": true,
           "requires": {
@@ -3439,7 +3456,7 @@
         },
         "readable-stream": {
           "version": "1.0.34",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
           "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
           "dev": true,
           "requires": {
@@ -3532,7 +3549,7 @@
     "define-property": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
-      "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=",
+      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
       "dev": true,
       "requires": {
         "is-descriptor": "^1.0.2",
@@ -3542,7 +3559,7 @@
         "is-accessor-descriptor": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
-          "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
           "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
@@ -3551,7 +3568,7 @@
         "is-data-descriptor": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
-          "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
           "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
@@ -3560,7 +3577,7 @@
         "is-descriptor": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
-          "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
           "dev": true,
           "requires": {
             "is-accessor-descriptor": "^1.0.0",
@@ -3828,7 +3845,7 @@
             },
             "readable-stream": {
               "version": "1.0.34",
-              "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
               "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
               "dev": true,
               "requires": {
@@ -3925,7 +3942,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -3941,7 +3958,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -4034,7 +4051,7 @@
         "end-of-stream": {
           "version": "1.4.1",
           "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
-          "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=",
+          "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
           "dev": true,
           "requires": {
             "once": "^1.4.0"
@@ -4048,7 +4065,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -4064,7 +4081,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -4246,7 +4263,7 @@
     "errno": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
-      "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=",
+      "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
       "dev": true,
       "optional": true,
       "requires": {
@@ -4955,7 +4972,7 @@
     "extglob": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
-      "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=",
+      "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
       "dev": true,
       "requires": {
         "array-unique": "^0.3.2",
@@ -4989,7 +5006,7 @@
         "is-accessor-descriptor": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
-          "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
           "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
@@ -4998,7 +5015,7 @@
         "is-data-descriptor": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
-          "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
           "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
@@ -5007,7 +5024,7 @@
         "is-descriptor": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
-          "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
           "dev": true,
           "requires": {
             "is-accessor-descriptor": "^1.0.0",
@@ -5505,8 +5522,7 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
@@ -5516,8 +5532,7 @@
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -5634,8 +5649,7 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
@@ -5647,7 +5661,6 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -5773,8 +5786,7 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -5786,7 +5798,6 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -5908,7 +5919,6 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -6049,7 +6059,7 @@
     },
     "get-stream": {
       "version": "3.0.0",
-      "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
       "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
       "dev": true,
       "optional": true
@@ -6279,7 +6289,7 @@
     "global-modules": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
-      "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=",
+      "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
       "dev": true,
       "requires": {
         "global-prefix": "^1.0.1",
@@ -6391,7 +6401,7 @@
     },
     "got": {
       "version": "5.7.1",
-      "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz",
+      "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
       "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
       "dev": true,
       "requires": {
@@ -6429,7 +6439,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -6517,6 +6527,18 @@
         }
       }
     },
+    "gulp-clean-css": {
+      "version": "3.10.0",
+      "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-3.10.0.tgz",
+      "integrity": "sha512-7Isf9Y690o/Q5MVjEylH1H7L8WeZ89woW7DnhD5unTintOdZb67KdOayRgp9trUFo+f9UyJtuatV42e/+kghPg==",
+      "dev": true,
+      "requires": {
+        "clean-css": "4.2.1",
+        "plugin-error": "1.0.1",
+        "through2": "2.0.3",
+        "vinyl-sourcemaps-apply": "0.2.1"
+      }
+    },
     "gulp-concat": {
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz",
@@ -6595,7 +6617,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -6611,7 +6633,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -6928,7 +6950,7 @@
         },
         "lodash": {
           "version": "2.4.1",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz",
+          "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz",
           "integrity": "sha1-W3cjA03aTSYuWkb7LFjXzCL3FCA=",
           "dev": true
         },
@@ -8022,7 +8044,7 @@
     "is-absolute": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
-      "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=",
+      "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
       "dev": true,
       "requires": {
         "is-relative": "^1.0.0",
@@ -8126,7 +8148,7 @@
     "is-descriptor": {
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
-      "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=",
+      "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
       "dev": true,
       "requires": {
         "is-accessor-descriptor": "^0.1.6",
@@ -8137,7 +8159,7 @@
         "kind-of": {
           "version": "5.1.0",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
-          "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=",
+          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
           "dev": true
         }
       }
@@ -8268,7 +8290,7 @@
     },
     "is-obj": {
       "version": "1.0.1",
-      "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
       "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
       "dev": true
     },
@@ -8360,7 +8382,7 @@
     "is-relative": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
-      "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=",
+      "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
       "dev": true,
       "requires": {
         "is-unc-path": "^1.0.0"
@@ -8417,7 +8439,7 @@
     "is-unc-path": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
-      "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=",
+      "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
       "dev": true,
       "requires": {
         "unc-path-regex": "^0.1.2"
@@ -8444,7 +8466,7 @@
     "is-windows": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
-      "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=",
+      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
       "dev": true
     },
     "is-zip": {
@@ -8953,7 +8975,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -8969,7 +8991,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -9062,7 +9084,7 @@
     "livereload-js": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz",
-      "integrity": "sha1-w6si6Kr1vzUF2A0JjLrWdyZUjJo=",
+      "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==",
       "dev": true
     },
     "load-json-file": {
@@ -9300,7 +9322,7 @@
     "lodash.merge": {
       "version": "4.6.1",
       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
-      "integrity": "sha1-rcJdnLmbk5HFliTzefu6YNcRHVQ=",
+      "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==",
       "dev": true
     },
     "lodash.partialright": {
@@ -9719,7 +9741,7 @@
     "make-iterator": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz",
-      "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=",
+      "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==",
       "dev": true,
       "requires": {
         "kind-of": "^6.0.2"
@@ -9878,7 +9900,7 @@
     "micromatch": {
       "version": "3.1.10",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
-      "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=",
+      "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
       "dev": true,
       "requires": {
         "arr-diff": "^4.0.0",
@@ -9941,7 +9963,7 @@
     "mixin-deep": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
-      "integrity": "sha1-pJ5yaNzhoNlpjkUybFYm3zVD0P4=",
+      "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
       "dev": true,
       "requires": {
         "for-in": "^1.0.2",
@@ -9951,7 +9973,7 @@
         "is-extendable": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
-          "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
           "dev": true,
           "requires": {
             "is-plain-object": "^2.0.4"
@@ -14077,7 +14099,7 @@
     "promise": {
       "version": "7.3.1",
       "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
-      "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=",
+      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
       "dev": true,
       "optional": true,
       "requires": {
@@ -14290,7 +14312,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -14306,7 +14328,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -14513,7 +14535,7 @@
     "regex-not": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
-      "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=",
+      "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
       "dev": true,
       "requires": {
         "extend-shallow": "^3.0.2",
@@ -14749,7 +14771,7 @@
     "ret": {
       "version": "0.1.15",
       "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
-      "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=",
+      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
       "dev": true
     },
     "right-align": {
@@ -15057,7 +15079,7 @@
     "set-value": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
-      "integrity": "sha1-ca5KiPD+77v1LR6mBPP7MV67YnQ=",
+      "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
       "dev": true,
       "requires": {
         "extend-shallow": "^2.0.1",
@@ -15162,7 +15184,7 @@
     "snapdragon": {
       "version": "0.8.2",
       "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
-      "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=",
+      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
       "dev": true,
       "requires": {
         "base": "^0.11.1",
@@ -15198,7 +15220,7 @@
     "snapdragon-node": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
-      "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=",
+      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
       "dev": true,
       "requires": {
         "define-property": "^1.0.0",
@@ -15218,7 +15240,7 @@
         "is-accessor-descriptor": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
-          "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
           "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
@@ -15227,7 +15249,7 @@
         "is-data-descriptor": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
-          "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
           "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
@@ -15236,7 +15258,7 @@
         "is-descriptor": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
-          "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
           "dev": true,
           "requires": {
             "is-accessor-descriptor": "^1.0.0",
@@ -15249,7 +15271,7 @@
     "snapdragon-util": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
-      "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=",
+      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
       "dev": true,
       "requires": {
         "kind-of": "^3.2.0"
@@ -15448,7 +15470,7 @@
     "split-string": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
-      "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=",
+      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
       "dev": true,
       "requires": {
         "extend-shallow": "^3.0.0"
@@ -15574,7 +15596,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -15590,7 +15612,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -15765,7 +15787,7 @@
     },
     "strip-dirs": {
       "version": "1.1.1",
-      "resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
       "integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=",
       "dev": true,
       "requires": {
@@ -15794,7 +15816,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -15955,7 +15977,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -16272,7 +16294,7 @@
     "to-regex": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
-      "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=",
+      "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
       "dev": true,
       "requires": {
         "define-property": "^2.0.2",
@@ -16372,7 +16394,7 @@
     "type-is": {
       "version": "1.6.16",
       "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
-      "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=",
+      "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
       "dev": true,
       "requires": {
         "media-typer": "0.3.0",
@@ -16720,7 +16742,7 @@
     "vendors": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz",
-      "integrity": "sha1-f8te759WI7FWvOqJ7DfWNnbyGAE=",
+      "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==",
       "dev": true
     },
     "verror": {
@@ -16777,7 +16799,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "requires": {
@@ -16793,7 +16815,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -16994,7 +17016,7 @@
     "websocket-extensions": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
-      "integrity": "sha1-XS/yKXcAPsaHpLhwc9+7rBRszyk=",
+      "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
       "dev": true
     },
     "when": {
diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json
index 21e0eafc33..e5948367da 100644
--- a/src/Umbraco.Web.UI.Client/package.json
+++ b/src/Umbraco.Web.UI.Client/package.json
@@ -44,6 +44,7 @@
     "@babel/preset-env": "^7.1.0",
     "autoprefixer": "^6.5.0",
     "bower-installer": "^1.2.0",
+    "gulp-clean-css": "3.10.0",
     "cssnano": "^3.7.6",
     "gulp": "^3.9.1",
     "gulp-babel": "^8.0.0-beta.2",

From df2313f7ec0e8a1189f01243d4c5fddf35841715 Mon Sep 17 00:00:00 2001
From: Anders Bjerner 
Date: Sun, 21 Oct 2018 15:36:17 +0200
Subject: [PATCH 204/585] umbRequestHelper.resourcePromise not supports the
 error message being a promise

---
 .../services/umbrequesthelper.service.js      | 919 +++++++++---------
 1 file changed, 464 insertions(+), 455 deletions(-)

diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
index d950d39619..fa6099b226 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
@@ -1,455 +1,464 @@
-/**
-* @ngdoc service
-* @name umbraco.services.umbRequestHelper
-* @description A helper object used for sending requests to the server
-**/
-function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) {
-    return {
-
-        /**
-         * @ngdoc method
-         * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath
-         * @methodOf umbraco.services.umbRequestHelper
-         * @function
-         *
-         * @description
-         * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path
-         * 
-         * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown
-         */
-        convertVirtualToAbsolutePath: function(virtualPath) {
-            if (virtualPath.startsWith("/")) {
-                return virtualPath;
-            }
-            if (!virtualPath.startsWith("~/")) {
-                throw "The path " + virtualPath + " is not a virtual path";
-            }
-            if (!Umbraco.Sys.ServerVariables.application.applicationPath) { 
-                throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath";
-            }
-            return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/");
-        },
-
-        /**
-         * @ngdoc method
-         * @name umbraco.services.umbRequestHelper#dictionaryToQueryString
-         * @methodOf umbraco.services.umbRequestHelper
-         * @function
-         *
-         * @description
-         * This will turn an array of key/value pairs or a standard dictionary into a query string
-         * 
-         * @param {Array} queryStrings An array of key/value pairs
-         */
-        dictionaryToQueryString: function (queryStrings) {
-            
-            if (angular.isArray(queryStrings)) {
-                return _.map(queryStrings, function (item) {
-                    var key = null;
-                    var val = null;
-                    for (var k in item) {
-                        key = k;
-                        val = item[k];
-                        break;
-                    }
-                    if (key === null || val === null) {
-                        throw "The object in the array was not formatted as a key/value pair";
-                    }
-                    return encodeURIComponent(key) + "=" + encodeURIComponent(val);
-                }).join("&");
-            }
-            else if (angular.isObject(queryStrings)) {
-
-                //this allows for a normal object to be passed in (ie. a dictionary)
-                return decodeURIComponent($.param(queryStrings));
-            }
-            
-            throw "The queryString parameter is not an array or object of key value pairs";
-        },
-
-        /**
-         * @ngdoc method
-         * @name umbraco.services.umbRequestHelper#getApiUrl
-         * @methodOf umbraco.services.umbRequestHelper
-         * @function
-         *
-         * @description
-         * This will return the webapi Url for the requested key based on the servervariables collection
-         * 
-         * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary
-         * @param {string} actionName The webapi action name 
-         * @param {object} queryStrings Can be either a string or an array containing key/value pairs
-         */
-        getApiUrl: function (apiName, actionName, queryStrings) {
-            if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) {
-                throw "No server variables defined!";
-            }
-
-            if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) {
-                throw "No url found for api name " + apiName;
-            }
-
-            return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName +
-                (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings)));
-
-        },
-
-        /**
-         * @ngdoc function
-         * @name umbraco.services.umbRequestHelper#resourcePromise
-         * @methodOf umbraco.services.umbRequestHelper
-         * @function
-         *
-         * @description
-         * This returns a promise with an underlying http call, it is a helper method to reduce
-         *  the amount of duplicate code needed to query http resources and automatically handle any 
-         *  Http errors. See /docs/source/using-promises-resources.md
-         *
-         * @param {object} opts A mixed object which can either be a string representing the error message to be
-         *   returned OR an object containing either:
-         *     { success: successCallback, errorMsg: errorMessage }
-         *          OR
-         *     { success: successCallback, error: errorCallback }
-         *   In both of the above, the successCallback must accept these parameters: data, status, headers, config
-         *   If using the errorCallback it must accept these parameters: data, status, headers, config
-         *   The success callback must return the data which will be resolved by the deferred object.
-         *   The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status }
-         */
-        resourcePromise: function (httpPromise, opts) {
-            var deferred = $q.defer();
-
-            /** The default success callback used if one is not supplied in the opts */
-            function defaultSuccess(data, status, headers, config) {
-                //when it's successful, just return the data
-                return data;
-            }
-
-            /** The default error callback used if one is not supplied in the opts */
-            function defaultError(data, status, headers, config) {
-                return {
-                    //NOTE: the default error message here should never be used based on the above docs!
-                    errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'),
-                    data: data,
-                    status: status
-                };
-            }
-
-            //create the callbacs based on whats been passed in.
-            var callbacks = {
-                success: ((!opts || !opts.success) ? defaultSuccess : opts.success),
-                error: ((!opts || !opts.error) ? defaultError : opts.error)
-            };
-
-            httpPromise.success(function (data, status, headers, config) {
-
-                //invoke the callback 
-                var result = callbacks.success.apply(this, [data, status, headers, config]);
-
-                //when it's successful, just return the data
-                deferred.resolve(result);
-
-            }).error(function (data, status, headers, config) {
-
-                //invoke the callback
-                var result = callbacks.error.apply(this, [data, status, headers, config]);
-
-                //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled.
-                if (status >= 500 && status < 600) {
-
-                    //show a ysod dialog
-                    if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) {
-                        eventsService.emit('app.ysod',
-                        {
-                            errorMsg: 'An error occured',
-                            data: data
-                        });
-                    }
-                    else {
-                        //show a simple error notification                         
-                        notificationsService.error("Server error", "Contact administrator, see log for full details.
" + result.errorMsg + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: result.errorMsg, - data: result.data, - status: result.status - }); - - - }); - - return deferred.promise; - - }, - - /** Used for saving media/content specifically */ - postSaveContent: function (args) { - - if (!args.restApiUrl) { - throw "args.restApiUrl is a required argument"; - } - if (!args.content) { - throw "args.content is a required argument"; - } - if (!args.action) { - throw "args.action is a required argument"; - } - if (!args.files) { - throw "args.files is a required argument"; - } - if (!args.dataFormatter) { - throw "args.dataFormatter is a required argument"; - } - - - var deferred = $q.defer(); - - //save the active tab id so we can set it when the data is returned. - var activeTab = _.find(args.content.tabs, function (item) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); - - //save the data - this.postMultiPartRequest( - args.restApiUrl, - { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, - function (data, formData) { - //now add all of the assigned files - for (var f in args.files) { - //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key - // so we know which property it belongs to on the server side - formData.append("file_" + args.files[f].alias, args.files[f].file); - } - - }, - function (data, status, headers, config) { - //success callback - - //reset the tabs and set the active one - if(data.tabs && data.tabs.length > 0) { - _.each(data.tabs, function (item) { - item.active = false; - }); - data.tabs[activeTabIndex].active = true; - } - - //the data returned is the up-to-date data so the UI will refresh - deferred.resolve(data); - }, - function (data, status, headers, config) { - //failure callback - - //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. - if (status >= 500 && status < 600) { - - //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, - // we have to just check for the existence of a string value but currently that is the best way to - // do this since it's very hacky/difficult to catch this on the server - if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { - notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); - } - else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - //show a ysod dialog - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: data - }); - } - else { - //show a simple error notification - notificationsService.error("Server error", "Contact administrator, see log for full details.
" + data.ExceptionMessage + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: 'An error occurred', - data: data, - status: status - }); - - - }); - - return deferred.promise; - }, - - /** Posts a multi-part mime request to the server */ - postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { - - //validate input, jsonData can be an array of key/value pairs or just one key/value pair. - if (!jsonData) { throw "jsonData cannot be null"; } - - if (angular.isArray(jsonData)) { - _.each(jsonData, function (item) { - if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } - }); - } - else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } - - - $http({ - method: 'POST', - url: url, - //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files - // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request - // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' - // will force the request to automatically populate the headers properly including the boundary parameter. - headers: { 'Content-Type': false }, - transformRequest: function (data) { - var formData = new FormData(); - //add the json data - if (angular.isArray(data)) { - _.each(data, function (item) { - formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); - }); - } - else { - formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); - } - - //call the callback - if (transformCallback) { - transformCallback.apply(this, [data, formData]); - } - - return formData; - }, - data: jsonData - }). - success(function (data, status, headers, config) { - if (successCallback) { - successCallback.apply(this, [data, status, headers, config]); - } - }). - error(function (data, status, headers, config) { - if (failureCallback) { - failureCallback.apply(this, [data, status, headers, config]); - } - }); - }, - - /** - * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 - */ - downloadFile : function (httpPath) { - - var deferred = $q.defer(); - - // Use an arraybuffer - $http.get(httpPath, { responseType: 'arraybuffer' }) - .success(function (data, status, headers) { - - var octetStreamMime = 'application/octet-stream'; - var success = false; - - // Get the headers - headers = headers(); - - // Get the filename from the x-filename header or default to "download.bin" - var filename = headers['x-filename'] || 'download.bin'; - - // Determine the content type from the header or default to "application/octet-stream" - var contentType = headers['content-type'] || octetStreamMime; - - try { - // Try using msSaveBlob if supported - console.log("Trying saveBlob method ..."); - var blob = new Blob([data], { type: contentType }); - if (navigator.msSaveBlob) - navigator.msSaveBlob(blob, filename); - else { - // Try using other saveBlob implementations, if available - var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; - if (saveBlob === undefined) throw "Not supported"; - saveBlob(blob, filename); - } - console.log("saveBlob succeeded"); - success = true; - } catch (ex) { - console.log("saveBlob method failed with the following exception:"); - console.log(ex); - } - - if (!success) { - // Get the blob url creator - var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; - if (urlCreator) { - // Try to use a download link - var link = document.createElement('a'); - if ('download' in link) { - // Try to simulate a click - try { - // Prepare a blob URL - console.log("Trying download link method with simulated click ..."); - var blob = new Blob([data], { type: contentType }); - var url = urlCreator.createObjectURL(blob); - link.setAttribute('href', url); - - // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) - link.setAttribute("download", filename); - - // Simulate clicking the download link - var event = document.createEvent('MouseEvents'); - event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); - link.dispatchEvent(event); - console.log("Download link method with simulated click succeeded"); - success = true; - - } catch (ex) { - console.log("Download link method with simulated click failed with the following exception:"); - console.log(ex); - } - } - - if (!success) { - // Fallback to window.location method - try { - // Prepare a blob URL - // Use application/octet-stream when using window.location to force download - console.log("Trying download link method with window.location ..."); - var blob = new Blob([data], { type: octetStreamMime }); - var url = urlCreator.createObjectURL(blob); - window.location = url; - console.log("Download link method with window.location succeeded"); - success = true; - } catch (ex) { - console.log("Download link method with window.location failed with the following exception:"); - console.log(ex); - } - } - - } - } - - if (!success) { - // Fallback to window.open method - console.log("No methods worked for saving the arraybuffer, using last resort window.open"); - window.open(httpPath, '_blank', ''); - } - - deferred.resolve(); - }) - .error(function (data, status) { - console.log("Request failed with status: " + status); - - deferred.reject({ - errorMsg: "An error occurred downloading the file", - data: data, - status: status - }); - }); - - return deferred.promise; - } - }; -} -angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); +/** +* @ngdoc service +* @name umbraco.services.umbRequestHelper +* @description A helper object used for sending requests to the server +**/ +function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) { + return { + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path + * + * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown + */ + convertVirtualToAbsolutePath: function(virtualPath) { + if (virtualPath.startsWith("/")) { + return virtualPath; + } + if (!virtualPath.startsWith("~/")) { + throw "The path " + virtualPath + " is not a virtual path"; + } + if (!Umbraco.Sys.ServerVariables.application.applicationPath) { + throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath"; + } + return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/"); + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#dictionaryToQueryString + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will turn an array of key/value pairs or a standard dictionary into a query string + * + * @param {Array} queryStrings An array of key/value pairs + */ + dictionaryToQueryString: function (queryStrings) { + + if (angular.isArray(queryStrings)) { + return _.map(queryStrings, function (item) { + var key = null; + var val = null; + for (var k in item) { + key = k; + val = item[k]; + break; + } + if (key === null || val === null) { + throw "The object in the array was not formatted as a key/value pair"; + } + return encodeURIComponent(key) + "=" + encodeURIComponent(val); + }).join("&"); + } + else if (angular.isObject(queryStrings)) { + + //this allows for a normal object to be passed in (ie. a dictionary) + return decodeURIComponent($.param(queryStrings)); + } + + throw "The queryString parameter is not an array or object of key value pairs"; + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#getApiUrl + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will return the webapi Url for the requested key based on the servervariables collection + * + * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary + * @param {string} actionName The webapi action name + * @param {object} queryStrings Can be either a string or an array containing key/value pairs + */ + getApiUrl: function (apiName, actionName, queryStrings) { + if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) { + throw "No server variables defined!"; + } + + if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) { + throw "No url found for api name " + apiName; + } + + return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName + + (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); + + }, + + /** + * @ngdoc function + * @name umbraco.services.umbRequestHelper#resourcePromise + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This returns a promise with an underlying http call, it is a helper method to reduce + * the amount of duplicate code needed to query http resources and automatically handle any + * Http errors. See /docs/source/using-promises-resources.md + * + * @param {object} opts A mixed object which can either be a string representing the error message to be + * returned OR an object containing either: + * { success: successCallback, errorMsg: errorMessage } + * OR + * { success: successCallback, error: errorCallback } + * In both of the above, the successCallback must accept these parameters: data, status, headers, config + * If using the errorCallback it must accept these parameters: data, status, headers, config + * The success callback must return the data which will be resolved by the deferred object. + * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } + */ + resourcePromise: function (httpPromise, opts) { + var deferred = $q.defer(); + + /** The default success callback used if one is not supplied in the opts */ + function defaultSuccess(data, status, headers, config) { + //when it's successful, just return the data + return data; + } + + /** The default error callback used if one is not supplied in the opts */ + function defaultError(data, status, headers, config) { + + var err = { + //NOTE: the default error message here should never be used based on the above docs! + errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), + data: data, + status: status + }; + + // if "opts" is a promise, we set "err.errorMsg" to be that promise + if (typeof(opts) == "object" && typeof(opts.then) == "function") { + err.errorMsg = opts; + } + + return err; + + } + + //create the callbacs based on whats been passed in. + var callbacks = { + success: ((!opts || !opts.success) ? defaultSuccess : opts.success), + error: ((!opts || !opts.error) ? defaultError : opts.error) + }; + + httpPromise.success(function (data, status, headers, config) { + + //invoke the callback + var result = callbacks.success.apply(this, [data, status, headers, config]); + + //when it's successful, just return the data + deferred.resolve(result); + + }).error(function (data, status, headers, config) { + + //invoke the callback + var result = callbacks.error.apply(this, [data, status, headers, config]); + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + + //show a ysod dialog + if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
" + result.errorMsg + ""); + } + + } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: result.errorMsg, + data: result.data, + status: result.status + }); + + + }); + + return deferred.promise; + + }, + + /** Used for saving media/content specifically */ + postSaveContent: function (args) { + + if (!args.restApiUrl) { + throw "args.restApiUrl is a required argument"; + } + if (!args.content) { + throw "args.content is a required argument"; + } + if (!args.action) { + throw "args.action is a required argument"; + } + if (!args.files) { + throw "args.files is a required argument"; + } + if (!args.dataFormatter) { + throw "args.dataFormatter is a required argument"; + } + + + var deferred = $q.defer(); + + //save the active tab id so we can set it when the data is returned. + var activeTab = _.find(args.content.tabs, function (item) { + return item.active; + }); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); + + //save the data + this.postMultiPartRequest( + args.restApiUrl, + { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, + function (data, formData) { + //now add all of the assigned files + for (var f in args.files) { + //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key + // so we know which property it belongs to on the server side + formData.append("file_" + args.files[f].alias, args.files[f].file); + } + + }, + function (data, status, headers, config) { + //success callback + + //reset the tabs and set the active one + if(data.tabs && data.tabs.length > 0) { + _.each(data.tabs, function (item) { + item.active = false; + }); + data.tabs[activeTabIndex].active = true; + } + + //the data returned is the up-to-date data so the UI will refresh + deferred.resolve(data); + }, + function (data, status, headers, config) { + //failure callback + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + + //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, + // we have to just check for the existence of a string value but currently that is the best way to + // do this since it's very hacky/difficult to catch this on the server + if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { + notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); + } + else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + //show a ysod dialog + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
" + data.ExceptionMessage + ""); + } + + } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: 'An error occurred', + data: data, + status: status + }); + + + }); + + return deferred.promise; + }, + + /** Posts a multi-part mime request to the server */ + postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { + + //validate input, jsonData can be an array of key/value pairs or just one key/value pair. + if (!jsonData) { throw "jsonData cannot be null"; } + + if (angular.isArray(jsonData)) { + _.each(jsonData, function (item) { + if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } + }); + } + else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } + + + $http({ + method: 'POST', + url: url, + //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files + // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request + // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' + // will force the request to automatically populate the headers properly including the boundary parameter. + headers: { 'Content-Type': false }, + transformRequest: function (data) { + var formData = new FormData(); + //add the json data + if (angular.isArray(data)) { + _.each(data, function (item) { + formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); + }); + } + else { + formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); + } + + //call the callback + if (transformCallback) { + transformCallback.apply(this, [data, formData]); + } + + return formData; + }, + data: jsonData + }). + success(function (data, status, headers, config) { + if (successCallback) { + successCallback.apply(this, [data, status, headers, config]); + } + }). + error(function (data, status, headers, config) { + if (failureCallback) { + failureCallback.apply(this, [data, status, headers, config]); + } + }); + }, + + /** + * Downloads a file to the client using AJAX/XHR + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + downloadFile : function (httpPath) { + + var deferred = $q.defer(); + + // Use an arraybuffer + $http.get(httpPath, { responseType: 'arraybuffer' }) + .success(function (data, status, headers) { + + var octetStreamMime = 'application/octet-stream'; + var success = false; + + // Get the headers + headers = headers(); + + // Get the filename from the x-filename header or default to "download.bin" + var filename = headers['x-filename'] || 'download.bin'; + + // Determine the content type from the header or default to "application/octet-stream" + var contentType = headers['content-type'] || octetStreamMime; + + try { + // Try using msSaveBlob if supported + console.log("Trying saveBlob method ..."); + var blob = new Blob([data], { type: contentType }); + if (navigator.msSaveBlob) + navigator.msSaveBlob(blob, filename); + else { + // Try using other saveBlob implementations, if available + var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; + if (saveBlob === undefined) throw "Not supported"; + saveBlob(blob, filename); + } + console.log("saveBlob succeeded"); + success = true; + } catch (ex) { + console.log("saveBlob method failed with the following exception:"); + console.log(ex); + } + + if (!success) { + // Get the blob url creator + var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; + if (urlCreator) { + // Try to use a download link + var link = document.createElement('a'); + if ('download' in link) { + // Try to simulate a click + try { + // Prepare a blob URL + console.log("Trying download link method with simulated click ..."); + var blob = new Blob([data], { type: contentType }); + var url = urlCreator.createObjectURL(blob); + link.setAttribute('href', url); + + // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) + link.setAttribute("download", filename); + + // Simulate clicking the download link + var event = document.createEvent('MouseEvents'); + event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(event); + console.log("Download link method with simulated click succeeded"); + success = true; + + } catch (ex) { + console.log("Download link method with simulated click failed with the following exception:"); + console.log(ex); + } + } + + if (!success) { + // Fallback to window.location method + try { + // Prepare a blob URL + // Use application/octet-stream when using window.location to force download + console.log("Trying download link method with window.location ..."); + var blob = new Blob([data], { type: octetStreamMime }); + var url = urlCreator.createObjectURL(blob); + window.location = url; + console.log("Download link method with window.location succeeded"); + success = true; + } catch (ex) { + console.log("Download link method with window.location failed with the following exception:"); + console.log(ex); + } + } + + } + } + + if (!success) { + // Fallback to window.open method + console.log("No methods worked for saving the arraybuffer, using last resort window.open"); + window.open(httpPath, '_blank', ''); + } + + deferred.resolve(); + }) + .error(function (data, status) { + console.log("Request failed with status: " + status); + + deferred.reject({ + errorMsg: "An error occurred downloading the file", + data: data, + status: status + }); + }); + + return deferred.promise; + } + }; +} +angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); From d16bc7a4659009a37ca7aab9d567cfff6c2d94d2 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 12 Oct 2018 08:34:33 +0200 Subject: [PATCH 205/585] Add a custom content item binder for saving blueprints (fixes #2985) --- src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../WebApi/Binders/BlueprintItemBinder.cs | 28 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 6268759e29..4643cb2a1e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -576,7 +576,7 @@ namespace Umbraco.Web.Editors [FileUploadCleanupFilter] [ContentPostValidate] public ContentItemDisplay PostSaveBlueprint( - [ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) + [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) { var contentItemDisplay = PostSaveInternal(contentItem, content => diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 66cce9b1bf..e021de0011 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -823,6 +823,7 @@ + diff --git a/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs new file mode 100644 index 0000000000..825c5b01c3 --- /dev/null +++ b/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs @@ -0,0 +1,28 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.WebApi.Binders +{ + internal class BlueprintItemBinder : ContentItemBinder + { + public BlueprintItemBinder(ApplicationContext applicationContext) + : base(applicationContext) + { + } + + /// + /// Constructor + /// + public BlueprintItemBinder() + : this(ApplicationContext.Current) + { + } + + protected override IContent GetExisting(ContentItemSave model) + { + return ApplicationContext.Services.ContentService.GetBlueprintById(Convert.ToInt32(model.Id)); + } + } +} \ No newline at end of file From 44fbff4410191068dec508d9b55bbef2a2894836 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 16 Oct 2018 13:25:44 +0200 Subject: [PATCH 206/585] Hide "reset password" on the login screen if system emails can't be sent. --- .../src/views/common/dialogs/login.controller.js | 2 +- src/Umbraco.Web/Editors/BackOfficeServerVariables.cs | 3 ++- 2 files changed, 3 insertions(+), 2 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 9b703a0987..771718ac11 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 @@ -207,7 +207,7 @@ } } - $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; $scope.showLogin = function () { $scope.errorMsg = ""; diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 0444820bc4..6e27a8f8e5 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Editors var keepOnlyKeys = new Dictionary { {"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl"}}, - {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage"}}, + {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage", "canSendRequiredEmail"}}, {"application", new[] {"applicationPath", "cacheBuster"}}, {"isDebuggingEnabled", new string[] { }}, {"features", new [] {"disabledFeatures"}} @@ -311,6 +311,7 @@ namespace Umbraco.Web.Editors {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, {"showUserInvite", EmailSender.CanSendRequiredEmail}, + {"canSendRequiredEmail", EmailSender.CanSendRequiredEmail}, } }, { From 7eecf230afacd0908140d4a82fb969734af069c9 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Sun, 21 Oct 2018 16:00:11 +0200 Subject: [PATCH 207/585] 3364 - Suggestion: Make use of confirm action directive on repeatable textstring when deleting (#3365) --- .../less/components/umb-multiple-textbox.less | 17 ++++++++++++-- .../multipletextbox.controller.js | 22 ++++++++++++++++++- .../multipletextbox/multipletextbox.html | 19 +++++++++++----- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less index 21f59a3e2d..52cc7a9aaf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less @@ -1,4 +1,17 @@ -.umb-multiple-textbox .textbox-wrapper { +.umb-multiple-textbox{ + &__confirm{ + position: relative; + + &-action{ + margin: 0; + padding: 2px; + background: transparent; + border: 0 none; + } + } +} + +.umb-multiple-textbox .textbox-wrapper { align-items: center; margin-bottom: 15px; } @@ -7,7 +20,7 @@ margin-bottom: 0; } -.umb-multiple-textbox .textbox-wrapper i { +.umb-multiple-textbox .textbox-wrapper i:not(.icon-delete, .icon-check) { margin-right: 5px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js index acee0f5ce9..9f190aab41 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js @@ -2,6 +2,9 @@ var backspaceHits = 0; + // Set the visible prompt to -1 to ensure it will not be visible + $scope.promptIsVisible = "-1"; + $scope.sortableOptions = { axis: 'y', containment: 'parent', @@ -89,6 +92,9 @@ }; $scope.remove = function (index) { + // Make sure not to trigger other prompts when remove is triggered + $scope.hidePrompt(); + var remainder = []; for (var x = 0; x < $scope.model.value.length; x++) { if (x !== index) { @@ -98,6 +104,20 @@ $scope.model.value = remainder; }; + $scope.showPrompt = function (idx, item){ + + var i = $scope.model.value.indexOf(item); + + // Make the prompt visible for the clicked tag only + if (i === idx) { + $scope.promptIsVisible = i; + } + } + + $scope.hidePrompt = function(){ + $scope.promptIsVisible = "-1"; + } + } -angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html index 123385d681..db98225e5f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html @@ -5,11 +5,20 @@ -
- - + +
+ + + + +
+
Date: Sat, 20 Oct 2018 12:16:24 +0200 Subject: [PATCH 208/585] Border color for .add-on should be @purple-l3 --- src/Umbraco.Web.UI.Client/src/less/forms.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 5926ea2163..bbedfe1a0b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -594,7 +594,7 @@ div.help { text-align: center; text-shadow: 0 1px 0 @white; background-color: @gray-10; - border: 1px solid @gray-8; + border: 1px solid @purple-l3; } .add-on, .btn, From bb3beb406d4e76e4a37124d09c0e6a37a92deea0 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sat, 20 Oct 2018 12:26:16 +0200 Subject: [PATCH 209/585]
 elements should at least have white-space:
 pre-wrap

---
 src/Umbraco.Web.UI.Client/src/less/hacks.less | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less
index 18439b554c..6505dc6e62 100644
--- a/src/Umbraco.Web.UI.Client/src/less/hacks.less
+++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less
@@ -202,7 +202,7 @@ pre {
   //font-size: @baseFontSize - 1; // 14px to 13px
   color: @gray-2;
   line-height: @baseLineHeight;
-  white-space: pre-line; // 1
+  white-space: pre-wrap; // 1
   overflow-x: auto; // 1
   background-color: @gray-10;
   border: 1px solid @gray-8;
@@ -222,4 +222,4 @@ pre {
     background-color: transparent;
     border: 0;
   }
-}
\ No newline at end of file
+}

From 954893cf2b81acfd7805ca4c9bd2d4cf78eb00a6 Mon Sep 17 00:00:00 2001
From: Anders Bjerner 
Date: Sun, 21 Oct 2018 15:36:17 +0200
Subject: [PATCH 210/585] umbRequestHelper.resourcePromise not supports the
 error message being a promise

---
 .../services/umbrequesthelper.service.js      | 919 +++++++++---------
 1 file changed, 464 insertions(+), 455 deletions(-)

diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
index d950d39619..fa6099b226 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
@@ -1,455 +1,464 @@
-/**
-* @ngdoc service
-* @name umbraco.services.umbRequestHelper
-* @description A helper object used for sending requests to the server
-**/
-function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) {
-    return {
-
-        /**
-         * @ngdoc method
-         * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath
-         * @methodOf umbraco.services.umbRequestHelper
-         * @function
-         *
-         * @description
-         * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path
-         * 
-         * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown
-         */
-        convertVirtualToAbsolutePath: function(virtualPath) {
-            if (virtualPath.startsWith("/")) {
-                return virtualPath;
-            }
-            if (!virtualPath.startsWith("~/")) {
-                throw "The path " + virtualPath + " is not a virtual path";
-            }
-            if (!Umbraco.Sys.ServerVariables.application.applicationPath) { 
-                throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath";
-            }
-            return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/");
-        },
-
-        /**
-         * @ngdoc method
-         * @name umbraco.services.umbRequestHelper#dictionaryToQueryString
-         * @methodOf umbraco.services.umbRequestHelper
-         * @function
-         *
-         * @description
-         * This will turn an array of key/value pairs or a standard dictionary into a query string
-         * 
-         * @param {Array} queryStrings An array of key/value pairs
-         */
-        dictionaryToQueryString: function (queryStrings) {
-            
-            if (angular.isArray(queryStrings)) {
-                return _.map(queryStrings, function (item) {
-                    var key = null;
-                    var val = null;
-                    for (var k in item) {
-                        key = k;
-                        val = item[k];
-                        break;
-                    }
-                    if (key === null || val === null) {
-                        throw "The object in the array was not formatted as a key/value pair";
-                    }
-                    return encodeURIComponent(key) + "=" + encodeURIComponent(val);
-                }).join("&");
-            }
-            else if (angular.isObject(queryStrings)) {
-
-                //this allows for a normal object to be passed in (ie. a dictionary)
-                return decodeURIComponent($.param(queryStrings));
-            }
-            
-            throw "The queryString parameter is not an array or object of key value pairs";
-        },
-
-        /**
-         * @ngdoc method
-         * @name umbraco.services.umbRequestHelper#getApiUrl
-         * @methodOf umbraco.services.umbRequestHelper
-         * @function
-         *
-         * @description
-         * This will return the webapi Url for the requested key based on the servervariables collection
-         * 
-         * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary
-         * @param {string} actionName The webapi action name 
-         * @param {object} queryStrings Can be either a string or an array containing key/value pairs
-         */
-        getApiUrl: function (apiName, actionName, queryStrings) {
-            if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) {
-                throw "No server variables defined!";
-            }
-
-            if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) {
-                throw "No url found for api name " + apiName;
-            }
-
-            return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName +
-                (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings)));
-
-        },
-
-        /**
-         * @ngdoc function
-         * @name umbraco.services.umbRequestHelper#resourcePromise
-         * @methodOf umbraco.services.umbRequestHelper
-         * @function
-         *
-         * @description
-         * This returns a promise with an underlying http call, it is a helper method to reduce
-         *  the amount of duplicate code needed to query http resources and automatically handle any 
-         *  Http errors. See /docs/source/using-promises-resources.md
-         *
-         * @param {object} opts A mixed object which can either be a string representing the error message to be
-         *   returned OR an object containing either:
-         *     { success: successCallback, errorMsg: errorMessage }
-         *          OR
-         *     { success: successCallback, error: errorCallback }
-         *   In both of the above, the successCallback must accept these parameters: data, status, headers, config
-         *   If using the errorCallback it must accept these parameters: data, status, headers, config
-         *   The success callback must return the data which will be resolved by the deferred object.
-         *   The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status }
-         */
-        resourcePromise: function (httpPromise, opts) {
-            var deferred = $q.defer();
-
-            /** The default success callback used if one is not supplied in the opts */
-            function defaultSuccess(data, status, headers, config) {
-                //when it's successful, just return the data
-                return data;
-            }
-
-            /** The default error callback used if one is not supplied in the opts */
-            function defaultError(data, status, headers, config) {
-                return {
-                    //NOTE: the default error message here should never be used based on the above docs!
-                    errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'),
-                    data: data,
-                    status: status
-                };
-            }
-
-            //create the callbacs based on whats been passed in.
-            var callbacks = {
-                success: ((!opts || !opts.success) ? defaultSuccess : opts.success),
-                error: ((!opts || !opts.error) ? defaultError : opts.error)
-            };
-
-            httpPromise.success(function (data, status, headers, config) {
-
-                //invoke the callback 
-                var result = callbacks.success.apply(this, [data, status, headers, config]);
-
-                //when it's successful, just return the data
-                deferred.resolve(result);
-
-            }).error(function (data, status, headers, config) {
-
-                //invoke the callback
-                var result = callbacks.error.apply(this, [data, status, headers, config]);
-
-                //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled.
-                if (status >= 500 && status < 600) {
-
-                    //show a ysod dialog
-                    if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) {
-                        eventsService.emit('app.ysod',
-                        {
-                            errorMsg: 'An error occured',
-                            data: data
-                        });
-                    }
-                    else {
-                        //show a simple error notification                         
-                        notificationsService.error("Server error", "Contact administrator, see log for full details.
" + result.errorMsg + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: result.errorMsg, - data: result.data, - status: result.status - }); - - - }); - - return deferred.promise; - - }, - - /** Used for saving media/content specifically */ - postSaveContent: function (args) { - - if (!args.restApiUrl) { - throw "args.restApiUrl is a required argument"; - } - if (!args.content) { - throw "args.content is a required argument"; - } - if (!args.action) { - throw "args.action is a required argument"; - } - if (!args.files) { - throw "args.files is a required argument"; - } - if (!args.dataFormatter) { - throw "args.dataFormatter is a required argument"; - } - - - var deferred = $q.defer(); - - //save the active tab id so we can set it when the data is returned. - var activeTab = _.find(args.content.tabs, function (item) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); - - //save the data - this.postMultiPartRequest( - args.restApiUrl, - { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, - function (data, formData) { - //now add all of the assigned files - for (var f in args.files) { - //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key - // so we know which property it belongs to on the server side - formData.append("file_" + args.files[f].alias, args.files[f].file); - } - - }, - function (data, status, headers, config) { - //success callback - - //reset the tabs and set the active one - if(data.tabs && data.tabs.length > 0) { - _.each(data.tabs, function (item) { - item.active = false; - }); - data.tabs[activeTabIndex].active = true; - } - - //the data returned is the up-to-date data so the UI will refresh - deferred.resolve(data); - }, - function (data, status, headers, config) { - //failure callback - - //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. - if (status >= 500 && status < 600) { - - //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, - // we have to just check for the existence of a string value but currently that is the best way to - // do this since it's very hacky/difficult to catch this on the server - if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { - notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); - } - else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - //show a ysod dialog - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: data - }); - } - else { - //show a simple error notification - notificationsService.error("Server error", "Contact administrator, see log for full details.
" + data.ExceptionMessage + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: 'An error occurred', - data: data, - status: status - }); - - - }); - - return deferred.promise; - }, - - /** Posts a multi-part mime request to the server */ - postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { - - //validate input, jsonData can be an array of key/value pairs or just one key/value pair. - if (!jsonData) { throw "jsonData cannot be null"; } - - if (angular.isArray(jsonData)) { - _.each(jsonData, function (item) { - if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } - }); - } - else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } - - - $http({ - method: 'POST', - url: url, - //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files - // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request - // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' - // will force the request to automatically populate the headers properly including the boundary parameter. - headers: { 'Content-Type': false }, - transformRequest: function (data) { - var formData = new FormData(); - //add the json data - if (angular.isArray(data)) { - _.each(data, function (item) { - formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); - }); - } - else { - formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); - } - - //call the callback - if (transformCallback) { - transformCallback.apply(this, [data, formData]); - } - - return formData; - }, - data: jsonData - }). - success(function (data, status, headers, config) { - if (successCallback) { - successCallback.apply(this, [data, status, headers, config]); - } - }). - error(function (data, status, headers, config) { - if (failureCallback) { - failureCallback.apply(this, [data, status, headers, config]); - } - }); - }, - - /** - * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 - */ - downloadFile : function (httpPath) { - - var deferred = $q.defer(); - - // Use an arraybuffer - $http.get(httpPath, { responseType: 'arraybuffer' }) - .success(function (data, status, headers) { - - var octetStreamMime = 'application/octet-stream'; - var success = false; - - // Get the headers - headers = headers(); - - // Get the filename from the x-filename header or default to "download.bin" - var filename = headers['x-filename'] || 'download.bin'; - - // Determine the content type from the header or default to "application/octet-stream" - var contentType = headers['content-type'] || octetStreamMime; - - try { - // Try using msSaveBlob if supported - console.log("Trying saveBlob method ..."); - var blob = new Blob([data], { type: contentType }); - if (navigator.msSaveBlob) - navigator.msSaveBlob(blob, filename); - else { - // Try using other saveBlob implementations, if available - var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; - if (saveBlob === undefined) throw "Not supported"; - saveBlob(blob, filename); - } - console.log("saveBlob succeeded"); - success = true; - } catch (ex) { - console.log("saveBlob method failed with the following exception:"); - console.log(ex); - } - - if (!success) { - // Get the blob url creator - var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; - if (urlCreator) { - // Try to use a download link - var link = document.createElement('a'); - if ('download' in link) { - // Try to simulate a click - try { - // Prepare a blob URL - console.log("Trying download link method with simulated click ..."); - var blob = new Blob([data], { type: contentType }); - var url = urlCreator.createObjectURL(blob); - link.setAttribute('href', url); - - // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) - link.setAttribute("download", filename); - - // Simulate clicking the download link - var event = document.createEvent('MouseEvents'); - event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); - link.dispatchEvent(event); - console.log("Download link method with simulated click succeeded"); - success = true; - - } catch (ex) { - console.log("Download link method with simulated click failed with the following exception:"); - console.log(ex); - } - } - - if (!success) { - // Fallback to window.location method - try { - // Prepare a blob URL - // Use application/octet-stream when using window.location to force download - console.log("Trying download link method with window.location ..."); - var blob = new Blob([data], { type: octetStreamMime }); - var url = urlCreator.createObjectURL(blob); - window.location = url; - console.log("Download link method with window.location succeeded"); - success = true; - } catch (ex) { - console.log("Download link method with window.location failed with the following exception:"); - console.log(ex); - } - } - - } - } - - if (!success) { - // Fallback to window.open method - console.log("No methods worked for saving the arraybuffer, using last resort window.open"); - window.open(httpPath, '_blank', ''); - } - - deferred.resolve(); - }) - .error(function (data, status) { - console.log("Request failed with status: " + status); - - deferred.reject({ - errorMsg: "An error occurred downloading the file", - data: data, - status: status - }); - }); - - return deferred.promise; - } - }; -} -angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); +/** +* @ngdoc service +* @name umbraco.services.umbRequestHelper +* @description A helper object used for sending requests to the server +**/ +function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) { + return { + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path + * + * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown + */ + convertVirtualToAbsolutePath: function(virtualPath) { + if (virtualPath.startsWith("/")) { + return virtualPath; + } + if (!virtualPath.startsWith("~/")) { + throw "The path " + virtualPath + " is not a virtual path"; + } + if (!Umbraco.Sys.ServerVariables.application.applicationPath) { + throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath"; + } + return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/"); + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#dictionaryToQueryString + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will turn an array of key/value pairs or a standard dictionary into a query string + * + * @param {Array} queryStrings An array of key/value pairs + */ + dictionaryToQueryString: function (queryStrings) { + + if (angular.isArray(queryStrings)) { + return _.map(queryStrings, function (item) { + var key = null; + var val = null; + for (var k in item) { + key = k; + val = item[k]; + break; + } + if (key === null || val === null) { + throw "The object in the array was not formatted as a key/value pair"; + } + return encodeURIComponent(key) + "=" + encodeURIComponent(val); + }).join("&"); + } + else if (angular.isObject(queryStrings)) { + + //this allows for a normal object to be passed in (ie. a dictionary) + return decodeURIComponent($.param(queryStrings)); + } + + throw "The queryString parameter is not an array or object of key value pairs"; + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#getApiUrl + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will return the webapi Url for the requested key based on the servervariables collection + * + * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary + * @param {string} actionName The webapi action name + * @param {object} queryStrings Can be either a string or an array containing key/value pairs + */ + getApiUrl: function (apiName, actionName, queryStrings) { + if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) { + throw "No server variables defined!"; + } + + if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) { + throw "No url found for api name " + apiName; + } + + return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName + + (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); + + }, + + /** + * @ngdoc function + * @name umbraco.services.umbRequestHelper#resourcePromise + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This returns a promise with an underlying http call, it is a helper method to reduce + * the amount of duplicate code needed to query http resources and automatically handle any + * Http errors. See /docs/source/using-promises-resources.md + * + * @param {object} opts A mixed object which can either be a string representing the error message to be + * returned OR an object containing either: + * { success: successCallback, errorMsg: errorMessage } + * OR + * { success: successCallback, error: errorCallback } + * In both of the above, the successCallback must accept these parameters: data, status, headers, config + * If using the errorCallback it must accept these parameters: data, status, headers, config + * The success callback must return the data which will be resolved by the deferred object. + * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } + */ + resourcePromise: function (httpPromise, opts) { + var deferred = $q.defer(); + + /** The default success callback used if one is not supplied in the opts */ + function defaultSuccess(data, status, headers, config) { + //when it's successful, just return the data + return data; + } + + /** The default error callback used if one is not supplied in the opts */ + function defaultError(data, status, headers, config) { + + var err = { + //NOTE: the default error message here should never be used based on the above docs! + errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), + data: data, + status: status + }; + + // if "opts" is a promise, we set "err.errorMsg" to be that promise + if (typeof(opts) == "object" && typeof(opts.then) == "function") { + err.errorMsg = opts; + } + + return err; + + } + + //create the callbacs based on whats been passed in. + var callbacks = { + success: ((!opts || !opts.success) ? defaultSuccess : opts.success), + error: ((!opts || !opts.error) ? defaultError : opts.error) + }; + + httpPromise.success(function (data, status, headers, config) { + + //invoke the callback + var result = callbacks.success.apply(this, [data, status, headers, config]); + + //when it's successful, just return the data + deferred.resolve(result); + + }).error(function (data, status, headers, config) { + + //invoke the callback + var result = callbacks.error.apply(this, [data, status, headers, config]); + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + + //show a ysod dialog + if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
" + result.errorMsg + ""); + } + + } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: result.errorMsg, + data: result.data, + status: result.status + }); + + + }); + + return deferred.promise; + + }, + + /** Used for saving media/content specifically */ + postSaveContent: function (args) { + + if (!args.restApiUrl) { + throw "args.restApiUrl is a required argument"; + } + if (!args.content) { + throw "args.content is a required argument"; + } + if (!args.action) { + throw "args.action is a required argument"; + } + if (!args.files) { + throw "args.files is a required argument"; + } + if (!args.dataFormatter) { + throw "args.dataFormatter is a required argument"; + } + + + var deferred = $q.defer(); + + //save the active tab id so we can set it when the data is returned. + var activeTab = _.find(args.content.tabs, function (item) { + return item.active; + }); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); + + //save the data + this.postMultiPartRequest( + args.restApiUrl, + { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, + function (data, formData) { + //now add all of the assigned files + for (var f in args.files) { + //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key + // so we know which property it belongs to on the server side + formData.append("file_" + args.files[f].alias, args.files[f].file); + } + + }, + function (data, status, headers, config) { + //success callback + + //reset the tabs and set the active one + if(data.tabs && data.tabs.length > 0) { + _.each(data.tabs, function (item) { + item.active = false; + }); + data.tabs[activeTabIndex].active = true; + } + + //the data returned is the up-to-date data so the UI will refresh + deferred.resolve(data); + }, + function (data, status, headers, config) { + //failure callback + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + + //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, + // we have to just check for the existence of a string value but currently that is the best way to + // do this since it's very hacky/difficult to catch this on the server + if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { + notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); + } + else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + //show a ysod dialog + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
" + data.ExceptionMessage + ""); + } + + } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: 'An error occurred', + data: data, + status: status + }); + + + }); + + return deferred.promise; + }, + + /** Posts a multi-part mime request to the server */ + postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { + + //validate input, jsonData can be an array of key/value pairs or just one key/value pair. + if (!jsonData) { throw "jsonData cannot be null"; } + + if (angular.isArray(jsonData)) { + _.each(jsonData, function (item) { + if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } + }); + } + else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } + + + $http({ + method: 'POST', + url: url, + //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files + // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request + // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' + // will force the request to automatically populate the headers properly including the boundary parameter. + headers: { 'Content-Type': false }, + transformRequest: function (data) { + var formData = new FormData(); + //add the json data + if (angular.isArray(data)) { + _.each(data, function (item) { + formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); + }); + } + else { + formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); + } + + //call the callback + if (transformCallback) { + transformCallback.apply(this, [data, formData]); + } + + return formData; + }, + data: jsonData + }). + success(function (data, status, headers, config) { + if (successCallback) { + successCallback.apply(this, [data, status, headers, config]); + } + }). + error(function (data, status, headers, config) { + if (failureCallback) { + failureCallback.apply(this, [data, status, headers, config]); + } + }); + }, + + /** + * Downloads a file to the client using AJAX/XHR + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + downloadFile : function (httpPath) { + + var deferred = $q.defer(); + + // Use an arraybuffer + $http.get(httpPath, { responseType: 'arraybuffer' }) + .success(function (data, status, headers) { + + var octetStreamMime = 'application/octet-stream'; + var success = false; + + // Get the headers + headers = headers(); + + // Get the filename from the x-filename header or default to "download.bin" + var filename = headers['x-filename'] || 'download.bin'; + + // Determine the content type from the header or default to "application/octet-stream" + var contentType = headers['content-type'] || octetStreamMime; + + try { + // Try using msSaveBlob if supported + console.log("Trying saveBlob method ..."); + var blob = new Blob([data], { type: contentType }); + if (navigator.msSaveBlob) + navigator.msSaveBlob(blob, filename); + else { + // Try using other saveBlob implementations, if available + var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; + if (saveBlob === undefined) throw "Not supported"; + saveBlob(blob, filename); + } + console.log("saveBlob succeeded"); + success = true; + } catch (ex) { + console.log("saveBlob method failed with the following exception:"); + console.log(ex); + } + + if (!success) { + // Get the blob url creator + var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; + if (urlCreator) { + // Try to use a download link + var link = document.createElement('a'); + if ('download' in link) { + // Try to simulate a click + try { + // Prepare a blob URL + console.log("Trying download link method with simulated click ..."); + var blob = new Blob([data], { type: contentType }); + var url = urlCreator.createObjectURL(blob); + link.setAttribute('href', url); + + // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) + link.setAttribute("download", filename); + + // Simulate clicking the download link + var event = document.createEvent('MouseEvents'); + event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(event); + console.log("Download link method with simulated click succeeded"); + success = true; + + } catch (ex) { + console.log("Download link method with simulated click failed with the following exception:"); + console.log(ex); + } + } + + if (!success) { + // Fallback to window.location method + try { + // Prepare a blob URL + // Use application/octet-stream when using window.location to force download + console.log("Trying download link method with window.location ..."); + var blob = new Blob([data], { type: octetStreamMime }); + var url = urlCreator.createObjectURL(blob); + window.location = url; + console.log("Download link method with window.location succeeded"); + success = true; + } catch (ex) { + console.log("Download link method with window.location failed with the following exception:"); + console.log(ex); + } + } + + } + } + + if (!success) { + // Fallback to window.open method + console.log("No methods worked for saving the arraybuffer, using last resort window.open"); + window.open(httpPath, '_blank', ''); + } + + deferred.resolve(); + }) + .error(function (data, status) { + console.log("Request failed with status: " + status); + + deferred.reject({ + errorMsg: "An error occurred downloading the file", + data: data, + status: status + }); + }); + + return deferred.promise; + } + }; +} +angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); From 96d7a776b7eddf8a21d4c59079adefafb79c292f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 21 Oct 2018 18:17:08 +0200 Subject: [PATCH 211/585] Removes dangling comma --- .../common/directives/components/umbGenerateAlias.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js index b3accc18b4..9b4bd17a12 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js @@ -70,7 +70,7 @@ angular.module("umbraco.directives") scope.labels = { idle: "Enter alias...", - busy: "Generating alias...", + busy: "Generating alias..." }; scope.placeholderText = scope.labels.idle; From 10526cfc81f3197336e6bdb543841aee5adfde50 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 21 Oct 2018 12:39:20 +0200 Subject: [PATCH 212/585] Fix #3368 by watching changes and explicitly flagging the form as dirty --- .../src/views/documenttypes/edit.controller.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 289bf607c3..cafe91e0eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -9,7 +9,7 @@ (function () { "use strict"; - function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) { + function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService, angularHelper) { var vm = this; var localizeSaving = localizationService.localize("general_saving"); @@ -375,6 +375,15 @@ eventsService.unsubscribe(evts[e]); } }); + + // #3368 - changes on the other "buttons" do not register on the current form, so we manually have to flag the form as dirty + $scope.$watch("vm.contentType.allowedContentTypes.length + vm.contentType.allowAsRoot + vm.contentType.allowedTemplates.length + vm.contentType.isContainer", function (newVal, oldVal) { + if (oldVal === undefined) { + // still initializing, ignore + return; + } + angularHelper.getCurrentForm($scope).$setDirty(); + }); } angular.module("umbraco").controller("Umbraco.Editors.DocumentTypes.EditController", DocumentTypesEditController); From 2c14f396fbbe5c529e76068e64404d63e02e954b Mon Sep 17 00:00:00 2001 From: Tim Geyssens Date: Thu, 18 Oct 2018 21:28:46 +0200 Subject: [PATCH 213/585] Update FileSystemTree.cs Make sure it can find child items in root folders --- .../umbraco.presentation/umbraco/Trees/FileSystemTree.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs index aed6009d86..06a5863da3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs @@ -42,8 +42,7 @@ namespace umbraco.cms.presentation.Trees if (!string.IsNullOrEmpty(this.NodeKey)) { orgPath = this.NodeKey; - path = IOHelper.MapPath(FilePath + orgPath); - orgPath += "/"; + path = IOHelper.MapPath(FilePath+ "/" + orgPath + "/"); } else { From 04aa1992667666d1768ef9d2afaa5483d76614b1 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 21 Oct 2018 18:57:07 +0200 Subject: [PATCH 214/585] Makes sure the path on the tree is correct --- .../umbraco.presentation/umbraco/Trees/FileSystemTree.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs index 06a5863da3..35a5b3129a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs @@ -8,6 +8,7 @@ using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.IO; +using Umbraco.Core; using Umbraco.Core.IO; namespace umbraco.cms.presentation.Trees @@ -41,8 +42,8 @@ namespace umbraco.cms.presentation.Trees string path = ""; if (!string.IsNullOrEmpty(this.NodeKey)) { - orgPath = this.NodeKey; - path = IOHelper.MapPath(FilePath+ "/" + orgPath + "/"); + orgPath = this.NodeKey.EnsureEndsWith('/'); + path = IOHelper.MapPath($"{FilePath.EnsureEndsWith('/')}{orgPath}"); } else { From dadc3ad7a7c4c5dcebe8edc18d6e041c7e5f708f Mon Sep 17 00:00:00 2001 From: James Coxhead Date: Thu, 18 Oct 2018 22:08:03 +0100 Subject: [PATCH 215/585] Fixes #3348 - Configure Umbraco.Dropdown.Flexible for new installations --- .../Persistence/Migrations/Initial/BaseDataCreation.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index d98bd81bfd..b8e605e63e 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -256,10 +256,10 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListMultipleAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Integer" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); @@ -300,6 +300,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //default's for MultipleMediaPickerAlias picker _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); + + // Defaults for single item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 7, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -42, Value = "0" }); + + // Defaults for multiple item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 8, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -39, Value = "1" }); } private void CreateUmbracoRelationTypeData() From 8c8d979eb20214e9c168ff831d5bc0a8afda98f3 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 11 Oct 2018 08:48:55 +0200 Subject: [PATCH 216/585] Style confirmations and alerts consistently across all sections (#3251) --- .../src/views/datatypes/move.html | 10 +++++++--- .../src/views/datatypes/rename.html | 6 ++++-- .../src/views/documenttypes/copy.html | 11 +++++++---- .../src/views/documenttypes/create.html | 12 ++++++++---- .../src/views/documenttypes/move.html | 11 +++++++---- .../src/views/documenttypes/rename.html | 6 ++++-- .../src/views/mediatypes/copy.html | 11 +++++++---- .../src/views/mediatypes/move.html | 10 +++++++--- .../src/views/mediatypes/rename.html | 6 ++++-- .../src/views/partialviewmacros/create.html | 6 ++++-- .../src/views/partialviews/create.html | 6 ++++-- .../src/views/scripts/create.html | 6 ++++-- 12 files changed, 67 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html index f05b8a6c79..0546cbff44 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html @@ -12,12 +12,16 @@
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
-
{{currentNode.name}} was moved underneath {{target.name}}
+
+ {{currentNode.name}} was moved underneath {{target.name}} +
diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/rename.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/rename.html index 6840e58565..ec93009a4c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/rename.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/rename.html @@ -7,8 +7,10 @@ val-form-manager>
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html index 2976f65917..97de821909 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html @@ -12,13 +12,16 @@
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
-
- {{currentNode.name}} was copied underneath {{target.name}}
+
+ {{currentNode.name}} was copied underneath {{target.name}} +
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 3519195848..b4818a51ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -50,8 +50,10 @@ val-form-manager>
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
@@ -74,8 +76,10 @@ val-form-manager>
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html index 1ed224b5ba..0af4cbfda7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html @@ -12,13 +12,16 @@
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
-
- {{currentNode.name}} was moved underneath {{target.name}}
+
+ {{currentNode.name}} was moved underneath {{target.name}} +
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/rename.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/rename.html index f739cadb71..c52b3be6d6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/rename.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/rename.html @@ -6,8 +6,10 @@ val-form-manager>
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html index fcc26b9928..77f50358c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html @@ -12,13 +12,16 @@
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
-
- {{currentNode.name}} was copied underneath {{target.name}}
+
+ {{currentNode.name}} was copied underneath {{target.name}} +
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html index 18f7aee337..c240925a7d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html @@ -12,12 +12,16 @@
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
-
{{currentNode.name}} was moved underneath {{target.name}}
+
+ {{currentNode.name}} was moved underneath {{target.name}} +
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/rename.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/rename.html index 5f027cf607..0bc4aa5123 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/rename.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/rename.html @@ -6,8 +6,10 @@ val-form-manager>
-
{{error.errorMsg}}
-

{{error.data.message}}

+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html index febaaab646..e2c7ab0af6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html @@ -61,8 +61,10 @@ val-form-manager>
-
{{vm.createFolderError.errorMsg}}
-

{{vm.createFolderError.data.message}}

+
+
{{vm.createFolderError.errorMsg}}
+
{{vm.createFolderError.data.message}}
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html index 7dc4268ce1..4d15ab6c01 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html @@ -51,8 +51,10 @@ val-form-manager>
-
{{vm.createFolderError.errorMsg}}
-

{{vm.createFolderError.data.message}}

+
+
{{vm.createFolderError.errorMsg}}
+
{{vm.createFolderError.data.message}}
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html index f7d8b0382d..6cda42f21a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html @@ -26,8 +26,10 @@ val-form-manager>
-
{{vm.createFolderError.errorMsg}}
-

{{vm.createFolderError.data.message}}

+
+
{{vm.createFolderError.errorMsg}}
+
{{vm.createFolderError.data.message}}
+
From b86be5b854f0b3d7bf5964bf7cab52f08ffc659d Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 11 Oct 2018 00:44:04 +0200 Subject: [PATCH 217/585] Fix color swatches title when using css classes for colors --- .../src/views/components/umb-color-swatches.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html index d038c9973c..2bc7ce9622 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html @@ -1,6 +1,6 @@ 
-