From 6fb6aa397f68a6574f12229f5dc570485a1a8a68 Mon Sep 17 00:00:00 2001 From: Anders Stentebjerg Date: Mon, 11 Nov 2013 15:34:44 +0100 Subject: [PATCH 01/14] Styling - Logo size login screen - H1 in panel --- src/Umbraco.Web.UI.Client/src/less/login.less | 4 +++- src/Umbraco.Web.UI.Client/src/less/navs.less | 2 +- src/Umbraco.Web.UI.Client/src/less/panel.less | 11 ++--------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/login.less b/src/Umbraco.Web.UI.Client/src/less/login.less index 4f5140aa16..d46bb41317 100644 --- a/src/Umbraco.Web.UI.Client/src/less/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/login.less @@ -4,7 +4,8 @@ .login-overlay { width: 100%; height: 100%; - background: @blackLight url(../img/application/logo.png) no-repeat 20px 30px fixed !important; + background: @blackLight url(../img/application/logo.png) no-repeat 25px 30px fixed !important; + background-size: 30px 30px !important; color: @white; position: absolute; z-index: 2000; @@ -12,6 +13,7 @@ left: 0px; margin: 0 !Important; padding: 0; + border-radius: 0 } .login-overlay .umb-modalcolumn{ diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index f4e5b9c610..c79f4b6420 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -298,7 +298,7 @@ .nav-pills .open .dropdown-toggle, .nav > li.dropdown.open.active > a:hover, .nav > li.dropdown.open.active > a:focus { - color: @white; + /*color: @white;*/ background-color: @grayLight; border-color: @grayLight; } diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index bdc552fbb5..97ab641eb6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -42,7 +42,7 @@ position: relative; } -.umb-panel-header .umb-headline { +.umb-panel-header .umb-headline, .umb-panel-header h1 { font-size: 18px; border: none; background: none; @@ -72,14 +72,7 @@ padding: 0px 0px 0px 20px; } -.umb-panel-header h1 { - margin: 0; - font-size: 14px; - font-weight: 400; - color: @gray; - line-height: 1em; - width: 100%; -} + .umb-panel-header p { margin:0px 20px; } From 6dcfbaee7f7ddbc019b5a360ce1b8ca72fc19ac0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 11:09:30 +1100 Subject: [PATCH 02/14] Adds a new filter arg to remove tree cache - we will use this in order to clear specific items from tree cache (like when we are creating folders for media in the media picker) --- .../src/common/services/tree.service.js | 33 ++++++++- .../src/common/services/util.service.js | 31 +++++++++ .../unit/common/services/tree-service.spec.js | 67 ++++++++++++++++--- 3 files changed, 118 insertions(+), 13 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 5b6bf1833c..7a310a5d3b 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 @@ -9,7 +9,11 @@ */ function treeService($q, treeResource, iconHelper, notificationsService, $rootScope) { - //TODO: implement this in local storage + //SD: Have looked at putting this in sessionStorage (not localStorage since that means you wouldn't be able to work + // in multiple tabs) - however our tree structure is cyclical, meaning a node has a reference to it's parent and it's children + // which you cannot serialize to sessionStorage. There's really no benefit of session storage except that you could refresh + // 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'; @@ -108,7 +112,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc return undefined; }, - /** clears the tree cache - with optional cacheKey and optional section */ + /** clears the tree cache - with optional cacheKey, optional section or optional filter */ clearCache: function (args) { //clear all if not specified if (!args) { @@ -122,6 +126,29 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc treeCache = _.omit(treeCache, cacheKey); } } + else if (args.filter && angular.isFunction(args.filter)) { + //if a filter is supplied a cacheKey must be supplied as well + if (!args.cacheKey) { + throw "args.cacheKey is required if args.filter is supplied"; + } + + //if a filter is supplied the function needs to return the data to keep + var byKey = treeCache[args.cacheKey]; + if (byKey) { + var result = args.filter(byKey); + + if (result) { + //set the result to the filtered data + treeCache[args.cacheKey] = result; + } + else { + //remove the cache + treeCache = _.omit(treeCache, args.cacheKey); + } + + } + + } else if (args.cacheKey) { //if only the cache key is specified, then clear all cache starting with that key var allKeys1 = _.keys(treeCache); @@ -137,7 +164,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc return k.endsWith("_" + args.section); }); treeCache = _.omit(treeCache, toRemove2); - } + } } }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 839411a90b..7892726d80 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -1,5 +1,36 @@ /*Contains multiple services for various helper tasks */ +/** + * @ngdoc function + * @name umbraco.services.umbSessionStorage + * @function + * + * @description + * Used to get/set things in browser sessionStorage but always prefixes keys with "umb_" and converts json vals so there is no overlap + * with any sessionStorage created by a developer. + */ +function umbSessionStorage($window) { + + //gets the sessionStorage object if available, otherwise just uses a normal object + // - required for unit tests. + var storage = $window['sessionStorage'] ? $window['sessionStorage'] : {}; + + return { + + get: function (key) { + console.log(storage); + console.log(storage["umb_" + key]); + return angular.fromJson(storage["umb_" + key]); + }, + + set : function(key, value) { + storage["umb_" + key] = angular.toJson(value); + } + + }; +} +angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorage); + /** * @ngdoc function * @name umbraco.services.legacyJsLoader diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js index cd79092385..499d8c1d6b 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js @@ -54,16 +54,6 @@ describe('tree service tests', function () { describe('tree cache', function () { - //it('tree with section but no cache key does not cache', function () { - // treeService.getTree().then(function (data) { - - // var cache = treeService._getTreeCache(); - // expect(cache).toBeDefined(); - // expect(cache["_content"]).toBeDefined(); - - // }); - //}); - it('does not cache with no args', function () { var cache; @@ -215,6 +205,63 @@ describe('tree service tests', function () { cache = treeService._getTreeCache(); expect(_.keys(cache).length).toBe(0); }); + + it('clears cache by key using a filter that returns null', function () { + + var cache; + + treeService.getTree({ section: "media", cacheKey: "_" }).then(function (d) { + treeService.getTree({ section: "content", cacheKey: "_" }).then(function (dd) { + cache = treeService._getTreeCache(); + }); + }); + + $rootScope.$digest(); + $httpBackend.flush(); + + expect(_.keys(cache).length).toBe(2); + + treeService.clearCache({ + cacheKey: "__content", + filter: function(currentCache) { + return null; + } + }); + + cache = treeService._getTreeCache(); + + expect(_.keys(cache).length).toBe(1); + }); + + it('removes cache by key using a filter', function () { + + var cache; + + treeService.getTree({ section: "media", cacheKey: "_" }).then(function (d) { + treeService.getTree({ section: "content", cacheKey: "_" }).then(function (dd) { + cache = treeService._getTreeCache(); + }); + }); + + $rootScope.$digest(); + $httpBackend.flush(); + + expect(_.keys(cache).length).toBe(2); + expect(cache.__content.root.children.length).toBe(4); + + treeService.clearCache({ + cacheKey: "__content", + filter: function (currentCache) { + var toRemove = treeService.getDescendantNode(currentCache.root, 1235); + toRemove.parent.children = _.without(toRemove.parent.children, toRemove); + return currentCache; + } + }); + + cache = treeService._getTreeCache(); + + expect(cache.__content.root.children.length).toBe(3); + }); }); From 635ccc444daa2bf8f5bbb9df52603ba8398b40cb Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 12:01:16 +1100 Subject: [PATCH 03/14] Adds another childrenOf option to clearing tree cache to clear a cached branch, this is used to fix U4-3465 Media tree not updating when creating folders and can be used elsewhere where modifying trees outside of it's context. --- .../src/common/services/tree.service.js | 21 +++++++++++++ .../common/dialogs/mediapicker.controller.js | 8 ++++- .../unit/common/services/tree-service.spec.js | 30 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) 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 7a310a5d3b..0ce43585d3 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 @@ -126,6 +126,27 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc treeCache = _.omit(treeCache, cacheKey); } } + else if (args.childrenOf) { + //if childrenOf is supplied a cacheKey must be supplied as well + 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 + // do this by recursing and specifying a filter + var self = this; + this.clearCache({ + cacheKey: args.cacheKey, + filter: function(cc) { + //get the new parent node from the tree cache + var parent = self.getDescendantNode(cc.root, args.childrenOf); + //clear it's children and set to not expanded + parent.children = null; + parent.expanded = false; + //return the cache to be saved + return cc; + } + }); + } else if (args.filter && angular.isFunction(args.filter)) { //if a filter is supplied a cacheKey must be supplied as well if (!args.cacheKey) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index 29270f7fcf..ba834e5e9e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Dialogs.MediaPickerController", - function($scope, mediaResource, umbRequestHelper, entityResource, $log, imageHelper, eventsService) { + function($scope, mediaResource, umbRequestHelper, entityResource, $log, imageHelper, eventsService, treeService) { var dialogOptions = $scope.$parent.dialogOptions; $scope.options = { @@ -21,6 +21,12 @@ angular.module("umbraco") .addFolder($scope.newFolderName, $scope.options.formData.currentFolder) .then(function(data) { + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: "__media", //this is the main media tree cache key + childrenOf: data.parentId //clear the children of the parent + }); + $scope.gotoFolder(data.id); }); } diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js index 499d8c1d6b..8659eadf07 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js @@ -262,6 +262,36 @@ describe('tree service tests', function () { expect(cache.__content.root.children.length).toBe(3); }); + + it('removes cache children for a parent id specified', function () { + + var cache; + + treeService.getTree({ section: "content", cacheKey: "_" }).then(function (dd) { + treeService.loadNodeChildren({ node: dd.root.children[0] }).then(function () { + cache = treeService._getTreeCache(); + }); + }); + + $rootScope.$digest(); + $httpBackend.flush(); + + expect(cache.__content.root.children.length).toBe(4); + expect(cache.__content.root.children[0].children.length).toBe(4); + + treeService.clearCache({ + cacheKey: "__content", + childrenOf: "1234" + }); + + cache = treeService._getTreeCache(); + + console.log(" blah: " + cache.__content.root.children.length); + + expect(cache.__content.root.children.length).toBe(4); + expect(cache.__content.root.children[0].children).toBeNull(); + expect(cache.__content.root.children[0].expanded).toBe(false); + }); }); From acd52d42f447966e28611c15b3ae2341bec78c4f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 12:30:10 +1100 Subject: [PATCH 04/14] Refactoring TreeNode to inherit from EntityBasic = makes much more sense and makes our models more consistent. --- .../src/common/services/search.service.js | 4 +-- .../Models/ContentEditing/EntityBasic.cs | 2 +- .../Models/Trees/SectionRootNode.cs | 2 +- src/Umbraco.Web/Models/Trees/TreeNode.cs | 33 ++++--------------- .../Trees/ApplicationTreeController.cs | 4 +-- .../Trees/LegacyTreeDataConverter.cs | 2 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 8 ++--- 7 files changed, 17 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 512ae0caf5..e9b41c9fc5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -5,7 +5,7 @@ angular.module('umbraco.services') member.menuUrl = umbRequestHelper.getApiUrl("memberTreeBaseUrl", "GetMenu", [{ id: member.id }, { application: 'member' }]); member.editorPath = "member/member/edit/" + (member.key ? member.key : member.id); member.metaData = { treeAlias: "member" }; - member.subTitle = member.additionalData.Email; + member.subTitle = member.metaData.Email; } function configureMediaResult(media) @@ -19,7 +19,7 @@ angular.module('umbraco.services') content.menuUrl = umbRequestHelper.getApiUrl("contentTreeBaseUrl", "GetMenu", [{ id: content.id }, { application: 'content' }]); content.editorPath = "content/content/edit/" + content.id; content.metaData = { treeAlias: "content" }; - content.subTitle = content.additionalData.Url; + content.subTitle = content.metaData.Url; } return { diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs index f5f779611f..428bb4ba3d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs @@ -53,7 +53,7 @@ namespace Umbraco.Web.Models.ContentEditing /// /// A collection of extra data that is available for this specific entity/entity type /// - [DataMember(Name = "additionalData")] + [DataMember(Name = "metaData")] public IDictionary AdditionalData { get; private set; } } } diff --git a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs index 4d5b34db5e..59b50a7792 100644 --- a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs +++ b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.Models.Trees return new SectionRootNode(nodeId, getChildNodesUrl, menuUrl) { Children = children, - Title = title + Name = title }; } diff --git a/src/Umbraco.Web/Models/Trees/TreeNode.cs b/src/Umbraco.Web/Models/Trees/TreeNode.cs index 03fa955426..c13fae94d9 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNode.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNode.cs @@ -2,6 +2,7 @@ using Umbraco.Core.IO; using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Trees { @@ -12,7 +13,7 @@ namespace Umbraco.Web.Models.Trees /// TreeNode is sealed to prevent developers from adding additional json data to the response /// [DataContract(Name = "node", Namespace = "")] - public class TreeNode + public class TreeNode : EntityBasic { /// /// Internal constructor, to create a tree node use the CreateTreeNode methods of the TreeApiController. @@ -22,35 +23,20 @@ namespace Umbraco.Web.Models.Trees /// internal TreeNode(string nodeId, string getChildNodesUrl, string menuUrl) { - //_menuItems = menuItems; - //Style = new NodeStyle(); - NodeId = nodeId; - AdditionalData = new Dictionary(); + Id = nodeId; ChildNodesUrl = getChildNodesUrl; MenuUrl = menuUrl; CssClasses = new List(); //default Icon = "icon-folder-close"; } - - /// - /// The unique identifier for the node - /// - [DataMember(Name = "id")] - public string NodeId { get; private set; } - + /// /// A flag to set whether or not this node has children /// [DataMember(Name = "hasChildren")] public bool HasChildren { get; set; } - - /// - /// The text title of the node that is displayed in the tree - /// - [DataMember(Name = "name")] - public string Title { get; set; } - + /// /// The tree nodetype which refers to the type of node rendered in the tree /// @@ -77,14 +63,7 @@ namespace Umbraco.Web.Models.Trees /// [DataMember(Name = "menuUrl")] public string MenuUrl { get; set; } - - /// - /// A dictionary to support any additional meta data that should be rendered for the node which is - /// useful for custom action commands such as 'create', 'copy', etc... - /// - [DataMember(Name = "metaData")] - public Dictionary AdditionalData { get; private set; } - + /// /// The icon to use for the node, this can be either a path to an image or a Css class. /// If a '/' is found in the string then it will be considered a path to an image. diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 3e96dee554..b16ee8bd00 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -80,7 +80,7 @@ namespace Umbraco.Web.Trees } var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); - multiTree.Title = ui.Text("sections", application); + multiTree.Name = ui.Text("sections", application); return multiTree; } @@ -133,7 +133,7 @@ namespace Umbraco.Web.Trees rootId, rootNode.Result.ChildNodesUrl, rootNode.Result.MenuUrl, - rootNode.Result.Title, + rootNode.Result.Name, byControllerAttempt.Result); foreach (var d in rootNode.Result.AdditionalData) diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 98100c771a..5aacb96f40 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -352,7 +352,7 @@ namespace Umbraco.Web.Trees { HasChildren = xmlTreeNode.HasChildren, Icon = xmlTreeNode.Icon, - Title = xmlTreeNode.Text, + Name = xmlTreeNode.Text, NodeType = xmlTreeNode.NodeType }; if (isRoot) diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 496418cdfd..35c8d75de5 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -160,7 +160,7 @@ namespace Umbraco.Web.Trees { HasChildren = true, RoutePath = currApp, - Title = RootNodeDisplayName + Name = RootNodeDisplayName }; return node; @@ -179,7 +179,7 @@ namespace Umbraco.Web.Trees { var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, jsonUrl, menuUrl) { Title = title }; + var node = new TreeNode(id, jsonUrl, menuUrl) { Name = title }; return node; } @@ -195,7 +195,7 @@ namespace Umbraco.Web.Trees { var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, jsonUrl, menuUrl) { Title = title, Icon = icon }; + var node = new TreeNode(id, jsonUrl, menuUrl) { Name = title, Icon = icon }; return node; } @@ -212,7 +212,7 @@ namespace Umbraco.Web.Trees { var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, jsonUrl, menuUrl) { Title = title, RoutePath = routePath, Icon = icon }; + var node = new TreeNode(id, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; return node; } From c4bfeeb321592cc6f975340a5512c71e2ddd3b56 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 13:09:24 +1100 Subject: [PATCH 05/14] Changes the CreateTreeNode to ensure that a parentId is always passed in - now all tree node models have a parentId property based on the server side data. Changes the JS tree node model to not have a 'parent' property and instead changes this to a parent() method - this allows us to serialize the tree model because otherwise it has cyclical references. --- .../src/common/services/tree.service.js | 29 ++++++++++++------- .../views/content/content.move.controller.js | 2 +- .../unit/common/services/tree-service.spec.js | 4 +-- .../Models/Trees/SectionRootNode.cs | 3 +- src/Umbraco.Web/Models/Trees/TreeNode.cs | 16 +++++----- .../Trees/ContentTreeController.cs | 1 + .../Trees/ContentTreeControllerBase.cs | 1 + .../Trees/DataTypeTreeController.cs | 1 + .../Trees/LegacyTreeDataConverter.cs | 2 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 2 +- src/Umbraco.Web/Trees/MemberTreeController.cs | 10 +++---- src/Umbraco.Web/Trees/TreeControllerBase.cs | 26 ++++++++++------- 12 files changed, 57 insertions(+), 40 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 0ce43585d3..6c03c4ed11 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 @@ -40,13 +40,22 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) { //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) { parentNode.section = section; } + //create a method outside of the loop to return the parent - otherwise jshint blows up + var funcParent = function() { + return parentNode; + }; for (var i = 0; i < treeNodes.length; i++) { treeNodes[i].level = childLevel; - treeNodes[i].parent = parentNode; + + //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. + treeNodes[i].parent = funcParent; //set the section for each tree node - this allows us to reference this easily when accessing tree nodes treeNodes[i].section = section; @@ -242,11 +251,11 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc /** Removes a given tree node from the tree */ removeNode: function(treeNode) { - if (treeNode.parent == null) { + if (treeNode.parent() == null) { 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); }, /** Removes all child nodes from a given tree node */ @@ -309,7 +318,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc root = current; } else { - current = current.parent; + current = current.parent(); } } return root; @@ -418,7 +427,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc if (!node) { throw "node cannot be null"; } - if (!node.parent) { + if (!node.parent()) { throw "cannot reload a single node without a parent"; } if (!node.section) { @@ -430,7 +439,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc //set the node to loading node.loading = true; - this.getChildren({ node: node.parent, section: node.section }).then(function(data) { + this.getChildren({ node: node.parent(), section: node.section }).then(function(data) { //ok, now that we have the children, find the node we're reloading var found = _.find(data, function(item) { @@ -438,14 +447,14 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc }); if (found) { //now we need to find the node in the parent.children collection to replace - var index = _.indexOf(node.parent.children, node); + var index = _.indexOf(node.parent().children, node); //the trick here is to not actually replace the node - this would cause the delete animations //to fire, instead we're just going to replace all the properties of this node. - _.extend(node.parent.children[index], found); + _.extend(node.parent().children[index], found); //set the node to loading - node.parent.children[index].loading = false; + node.parent().children[index].loading = false; //return - deferred.resolve(node.parent.children[index]); + deferred.resolve(node.parent().children[index]); } else { deferred.reject(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js index 7a44e6d580..cedb409117 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js @@ -39,7 +39,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController", $scope.success = true; //reloads the parent - navigationService.reloadNode(dialogOptions.currentNode.parent); + navigationService.reloadNode(dialogOptions.currentNode.parent()); //reloads the target navigationService.syncTree({ tree: "content", path: path, forceReload: true }); diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js index 8659eadf07..c4d5ca49a5 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js @@ -253,7 +253,7 @@ describe('tree service tests', function () { cacheKey: "__content", filter: function (currentCache) { var toRemove = treeService.getDescendantNode(currentCache.root, 1235); - toRemove.parent.children = _.without(toRemove.parent.children, toRemove); + toRemove.parent().children = _.without(toRemove.parent().children, toRemove); return currentCache; } }); @@ -286,8 +286,6 @@ describe('tree service tests', function () { cache = treeService._getTreeCache(); - console.log(" blah: " + cache.__content.root.children.length); - expect(cache.__content.root.children.length).toBe(4); expect(cache.__content.root.children[0].children).toBeNull(); expect(cache.__content.root.children[0].expanded).toBe(false); diff --git a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs index 59b50a7792..3116159f5f 100644 --- a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs +++ b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using Umbraco.Core; namespace Umbraco.Web.Models.Trees { @@ -33,7 +34,7 @@ namespace Umbraco.Web.Models.Trees } private SectionRootNode(string nodeId, string getChildNodesUrl, string menuUrl) - : base(nodeId, getChildNodesUrl, menuUrl) + : base(nodeId, null, getChildNodesUrl, menuUrl) { //default to false IsContainer = false; diff --git a/src/Umbraco.Web/Models/Trees/TreeNode.cs b/src/Umbraco.Web/Models/Trees/TreeNode.cs index c13fae94d9..c413edea37 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNode.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNode.cs @@ -19,11 +19,15 @@ namespace Umbraco.Web.Models.Trees /// Internal constructor, to create a tree node use the CreateTreeNode methods of the TreeApiController. /// /// + /// The parent id for the current node /// /// - internal TreeNode(string nodeId, string getChildNodesUrl, string menuUrl) + internal TreeNode(string nodeId, string parentId, string getChildNodesUrl, string menuUrl) { + Mandate.ParameterNotNullOrEmpty(nodeId, "nodeId"); + Id = nodeId; + ParentId = parentId; ChildNodesUrl = getChildNodesUrl; MenuUrl = menuUrl; CssClasses = new List(); @@ -31,6 +35,9 @@ namespace Umbraco.Web.Models.Trees Icon = "icon-folder-close"; } + [DataMember(Name = "parentId", IsRequired = true)] + public new object ParentId { get; set; } + /// /// A flag to set whether or not this node has children /// @@ -63,13 +70,6 @@ namespace Umbraco.Web.Models.Trees /// [DataMember(Name = "menuUrl")] public string MenuUrl { get; set; } - - /// - /// The icon to use for the node, this can be either a path to an image or a Css class. - /// If a '/' is found in the string then it will be considered a path to an image. - /// - [DataMember(Name = "icon")] - public string Icon { get; set; } /// /// Returns true if the icon represents a CSS class instead of a file path diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 5bfa20e030..9cfc2bcb14 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -75,6 +75,7 @@ namespace Umbraco.Web.Trees var node = CreateTreeNode( e.Id.ToInvariantString(), + id, queryStrings, e.Name, e.ContentTypeIcon, diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 352f0c3521..5b9a6181f3 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -72,6 +72,7 @@ namespace Umbraco.Web.Trees { nodes.Add(CreateTreeNode( RecycleBinId.ToInvariantString(), + id, queryStrings, ui.GetText("general", "recycleBin"), "icon-trash", diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 76d934c9cf..c41deb350b 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -33,6 +33,7 @@ namespace Umbraco.Web.Trees .Select(dt => CreateTreeNode( dt.Id.ToInvariantString(), + id, queryStrings, dt.Name, "icon-autofill", diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 5aacb96f40..9cce00779f 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -348,7 +348,7 @@ namespace Umbraco.Web.Trees //TODO: Might need to add stuff to additional attributes - var node = new TreeNode(xmlTreeNode.NodeID, childNodesSource, menuSource) + var node = new TreeNode(xmlTreeNode.NodeID, isRoot ? null : parentId, childNodesSource, menuSource) { HasChildren = xmlTreeNode.HasChildren, Icon = xmlTreeNode.Icon, diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index fd85910c8f..e8283b0f90 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -55,7 +55,7 @@ namespace Umbraco.Web.Trees foreach (var entity in entities) { var e = (UmbracoEntity)entity; - var node = CreateTreeNode(e.Id.ToInvariantString(), queryStrings, e.Name, e.ContentTypeIcon, e.HasChildren); + var node = CreateTreeNode(e.Id.ToInvariantString(), id, queryStrings, e.Name, e.ContentTypeIcon, e.HasChildren); node.AdditionalData.Add("contentType", e.ContentTypeAlias); nodes.Add(node); diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index f7d536c083..6d4c2eb743 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -30,14 +30,14 @@ namespace Umbraco.Web.Trees for (var i = 97; i < 123; i++) { var charString = ((char) i).ToString(CultureInfo.InvariantCulture); - var folder = CreateTreeNode(charString, queryStrings, charString, "icon-folder-close", true); + var folder = CreateTreeNode(charString, id, queryStrings, charString, "icon-folder-close", true); folder.NodeType = "member-folder"; nodes.Add(folder); } //list out 'Others' if the membership provider is umbraco if (Member.InUmbracoMemberMode()) { - var folder = CreateTreeNode("others", queryStrings, "Others", "icon-folder-close", true); + var folder = CreateTreeNode("others", id, queryStrings, "Others", "icon-folder-close", true); folder.NodeType = "member-folder"; nodes.Add(folder); } @@ -52,7 +52,7 @@ namespace Umbraco.Web.Trees //get the members from our member data layer nodes.AddRange( Member.getMemberFromFirstLetter(id.ToCharArray()[0]) - .Select(m => CreateTreeNode(m.UniqueId.ToString("N"), queryStrings, m.Text, "icon-user"))); + .Select(m => CreateTreeNode(m.UniqueId.ToString("N"), id, queryStrings, m.Text, "icon-user"))); } else { @@ -60,7 +60,7 @@ namespace Umbraco.Web.Trees int total; nodes.AddRange( Membership.Provider.FindUsersByName(id + "%", 0, 9999, out total).Cast() - .Select(m => CreateTreeNode(m.ProviderUserKey.ToString(), queryStrings, m.UserName, "icon-user"))); + .Select(m => CreateTreeNode(m.ProviderUserKey.ToString(), id, queryStrings, m.UserName, "icon-user"))); } } else if (id == "others") @@ -68,7 +68,7 @@ namespace Umbraco.Web.Trees //others will only show up when in umbraco membership mode nodes.AddRange( Member.getAllOtherMembers() - .Select(m => CreateTreeNode(m.Id.ToInvariantString(), queryStrings, m.Text, "icon-user"))); + .Select(m => CreateTreeNode(m.Id.ToInvariantString(), id, queryStrings, m.Text, "icon-user"))); } } return nodes; diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 35c8d75de5..e31cb85b0d 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -155,6 +155,7 @@ namespace Umbraco.Web.Trees var node = new TreeNode( rootNodeAsString, + null, //this is a root node, there is no parent Url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), Url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) { @@ -172,14 +173,15 @@ namespace Umbraco.Web.Trees /// Helper method to create tree nodes /// /// + /// /// /// /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title) + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title) { var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, jsonUrl, menuUrl) { Name = title }; + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title }; return node; } @@ -187,15 +189,16 @@ namespace Umbraco.Web.Trees /// Helper method to create tree nodes /// /// + /// /// /// /// /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon) + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon) { var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, jsonUrl, menuUrl) { Name = title, Icon = icon }; + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, Icon = icon }; return node; } @@ -203,16 +206,17 @@ namespace Umbraco.Web.Trees /// Helper method to create tree nodes /// /// + /// /// /// /// /// /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon, string routePath) + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, string routePath) { var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; return node; } @@ -220,14 +224,15 @@ namespace Umbraco.Web.Trees /// Helper method to create tree nodes and automatically generate the json url /// /// + /// /// /// /// /// /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon, bool hasChildren) + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren) { - var treeNode = CreateTreeNode(id, queryStrings, title, icon); + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); treeNode.HasChildren = hasChildren; return treeNode; } @@ -236,15 +241,16 @@ namespace Umbraco.Web.Trees /// Helper method to create tree nodes and automatically generate the json url /// /// + /// /// /// /// /// /// /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) { - var treeNode = CreateTreeNode(id, queryStrings, title, icon); + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); treeNode.HasChildren = hasChildren; treeNode.RoutePath = routePath; return treeNode; From 72b08f4029e8dfa5ea8dfb0b2cb69e32d9d032a6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 13:28:13 +1100 Subject: [PATCH 06/14] Fixes an issue if the upgrade check server is not responding. --- .../Editors/UpdateCheckController.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs index 57c20e2574..aa4966b14c 100644 --- a/src/Umbraco.Web/Editors/UpdateCheckController.cs +++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs @@ -20,13 +20,21 @@ namespace Umbraco.Web.Editors var updateCheckCookie = updChkCookie != null ? updChkCookie["UMB_UPDCHK"].Value : ""; if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.UserType.Alias == "admin") { - var check = new global::umbraco.presentation.org.umbraco.update.CheckForUpgrade(); - var result = check.CheckUpgrade(UmbracoVersion.Current.Major, - UmbracoVersion.Current.Minor, - UmbracoVersion.Current.Build, - UmbracoVersion.CurrentComment); - - return new UpgradeCheckResponse(result.UpgradeType.ToString(), result.Comment, result.UpgradeUrl); + try + { + var check = new global::umbraco.presentation.org.umbraco.update.CheckForUpgrade(); + var result = check.CheckUpgrade(UmbracoVersion.Current.Major, + UmbracoVersion.Current.Minor, + UmbracoVersion.Current.Build, + UmbracoVersion.CurrentComment); + + return new UpgradeCheckResponse(result.UpgradeType.ToString(), result.Comment, result.UpgradeUrl); + } + catch (System.Net.WebException) + { + //this occurs if the server is down or cannot be reached + return null; + } } return null; } @@ -41,6 +49,8 @@ namespace Umbraco.Web.Editors { public override void OnActionExecuted(HttpActionExecutedContext context) { + if (context.Response == null) return; + var objectContent = context.Response.Content as ObjectContent; if (objectContent == null || objectContent.Value == null) return; From c2fca795e66e548d6c3c79ebf1bbd5c6f78c6d30 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 15:02:02 +1100 Subject: [PATCH 07/14] This is an attempt to put the tree cache into sessionStorage - I'm creating this revision in case we want to re-visit this but unfortunately it is much more difficult because the cache is serialized/deserialized which means only the first level will be cached automatically since the loadNodeChildren would have to update the cache too but it doesn't know what a cache key is. I've then done it the other way which is that we cache the tree sections when changing sections, this works great but poses other issues due to the serialized nature of the tree nodes. Spending too much time here, will revisit some other day. will revert after this is committed. --- .../common/directives/umbtree.directive.js | 8 +- .../src/common/services/tree.service.js | 101 +++++++++++++----- .../src/common/services/util.service.js | 4 +- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js index e8f5d8b2bd..ccaf035d63 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js @@ -217,7 +217,11 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //anytime we want to load the tree we need to disable the delete animations deleteAnimations = false; - //use $q.when because a promise OR raw data might be returned. + //Here's where we cache the data before loading other data (changing sections) + if (lastSection && scope.section !== lastSection) { + treeService.cacheTree(scope.cachekey, lastSection, scope.tree); + } + treeService.getTree({ section: scope.section, tree: scope.treealias, cacheKey: scope.cachekey, isDialog: scope.isdialog ? scope.isdialog : false }) .then(function(data) { //set the data once we have it @@ -356,7 +360,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //When the user logs in scope.$on("authenticated", function(evt, data) { //populate the tree if the user has changed - if (data.lastUserId !== data.user.id) { + if (data.lastUserId && data.lastUserId !== data.user.id) { treeService.clearCache(); scope.tree = null; 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 6c03c4ed11..a58a3e63d4 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 @@ -7,14 +7,13 @@ * @description * The tree service factory, used internally by the umbTree and umbTreeItem directives */ -function treeService($q, treeResource, iconHelper, notificationsService, $rootScope) { +function treeService($q, treeResource, iconHelper, notificationsService, $rootScope, umbSessionStorage) { - //SD: Have looked at putting this in sessionStorage (not localStorage since that means you wouldn't be able to work - // in multiple tabs) - however our tree structure is cyclical, meaning a node has a reference to it's parent and it's children - // which you cannot serialize to sessionStorage. There's really no benefit of session storage except that you could refresh - // 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 = {}; + //initialize the tree cache if nothing is there, we store the cache in sessionStorage which + // is applicable to the current open tab + if (!umbSessionStorage.get("treeCache")) { + umbSessionStorage.set("treeCache", {}); + } var standardCssClass = 'icon umb-tree-icon sprTree'; @@ -31,9 +30,44 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc return { - /** Internal method to return the tree cache */ - _getTreeCache: function() { - return treeCache; + /** Internal method to return the tree cache - this also wires up the parent() function for each node since when we serialize to cache, functions are obviously lost */ + _getTreeCache: function(key) { + var cache = umbSessionStorage.get("treeCache"); + + //method to set the parent() delegate for each node + var setParent = function (currParent, currChildren) { + _.each(currChildren, function (child, index) { + //create the method, return it's parent + child.parent = function () { + return currParent; + }; + if (angular.isArray(child.children) && child.children.length > 0) { + //recurse + setParent(child, child.children); + } + + }); + }; + + //return the raw cache if a key is specified but there is nothing to process with that key + if (key && (!cache[key] || !cache[key].root || !angular.isArray(cache[key].root.children))) { + return cache; + } + else if (key && cache[key]) { + //if a key is specified only process that portion of the cache + setParent(cache[key].root, cache[key].root.children); + return cache; + } + else { + //no key is specified, process all of the cache + _.each(cache, function (val) { + if (val.root && angular.isArray(val.root.children)) { + setParent(val.root, val.root.children); + } + }); + return cache; + } + }, /** 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 */ @@ -121,18 +155,35 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc return undefined; }, + /** This puts the tree into cache */ + cacheTree: function (cacheKey, section, tree) { + if (!cacheKey || !section || !tree) { + return; + } + + var key = getCacheKey({ cacheKey: cacheKey, section: section }); + //NOTE: we're not using the _getTreeCache method here because we don't want to process the parent() method, + // simply to get the raw values so we can update it. + var rawCache = umbSessionStorage.get("treeCache"); + rawCache[key] = tree; + umbSessionStorage.set("treeCache", rawCache); + }, + /** clears the tree cache - with optional cacheKey, optional section or optional filter */ clearCache: function (args) { //clear all if not specified if (!args) { - treeCache = {}; + umbSessionStorage.set("treeCache", {}); } else { + + var treeCache = this._getTreeCache(); + //if section and cache key specified just clear that cache if (args.section && args.cacheKey) { - var cacheKey = getCacheKey(args); + var cacheKey = getCacheKey(args); if (cacheKey && treeCache && treeCache[cacheKey] != null) { - treeCache = _.omit(treeCache, cacheKey); + umbSessionStorage.set("treeCache", _.omit(treeCache, cacheKey)); } } else if (args.childrenOf) { @@ -170,12 +221,12 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc if (result) { //set the result to the filtered data treeCache[args.cacheKey] = result; + umbSessionStorage.set("treeCache", treeCache); } else { //remove the cache - treeCache = _.omit(treeCache, args.cacheKey); + umbSessionStorage.set("treeCache", _.omit(treeCache, args.cacheKey)); } - } } @@ -185,7 +236,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc var toRemove1 = _.filter(allKeys1, function (k) { return k.startsWith(args.cacheKey + "_"); }); - treeCache = _.omit(treeCache, toRemove1); + umbSessionStorage.set("treeCache", _.omit(treeCache, toRemove1)); } else if (args.section) { //if only the section is specified then clear all cache regardless of cache key by that section @@ -193,7 +244,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc var toRemove2 = _.filter(allKeys2, function (k) { return k.endsWith("_" + args.section); }); - treeCache = _.omit(treeCache, toRemove2); + umbSessionStorage.set("treeCache", _.omit(treeCache, toRemove2)); } } }, @@ -347,7 +398,8 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc } var cacheKey = getCacheKey(args); - + var treeCache = this._getTreeCache(cacheKey); + //return the cache if it exists if (cacheKey && treeCache[cacheKey] !== undefined) { deferred.resolve(treeCache[cacheKey]); @@ -366,12 +418,13 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc //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); - //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. - if (cacheKey) { - treeCache[cacheKey] = result; - deferred.resolve(treeCache[cacheKey]); - } + ////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. + //if (cacheKey) { + // treeCache[cacheKey] = result; + // umbSessionStorage.set("treeCache", treeCache); + // deferred.resolve(treeCache[cacheKey]); + //} //return un-cached deferred.resolve(result); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 7892726d80..9f86c7f17c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -17,9 +17,7 @@ function umbSessionStorage($window) { return { - get: function (key) { - console.log(storage); - console.log(storage["umb_" + key]); + get: function (key) { return angular.fromJson(storage["umb_" + key]); }, From a9ffa3f1b79dea56bb9a5acea68351423453f095 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 15:06:47 +1100 Subject: [PATCH 08/14] his reverts commit c2fca795e66e548d6c3c79ebf1bbd5c6f78c6d30 to use the normal tree cache --- .../common/directives/umbtree.directive.js | 8 +- .../src/common/services/tree.service.js | 101 +++++------------- .../src/common/services/util.service.js | 4 +- 3 files changed, 29 insertions(+), 84 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js index ccaf035d63..e8f5d8b2bd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js @@ -217,11 +217,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //anytime we want to load the tree we need to disable the delete animations deleteAnimations = false; - //Here's where we cache the data before loading other data (changing sections) - if (lastSection && scope.section !== lastSection) { - treeService.cacheTree(scope.cachekey, lastSection, scope.tree); - } - + //use $q.when because a promise OR raw data might be returned. treeService.getTree({ section: scope.section, tree: scope.treealias, cacheKey: scope.cachekey, isDialog: scope.isdialog ? scope.isdialog : false }) .then(function(data) { //set the data once we have it @@ -360,7 +356,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //When the user logs in scope.$on("authenticated", function(evt, data) { //populate the tree if the user has changed - if (data.lastUserId && data.lastUserId !== data.user.id) { + if (data.lastUserId !== data.user.id) { treeService.clearCache(); scope.tree = null; 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 a58a3e63d4..6c03c4ed11 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 @@ -7,13 +7,14 @@ * @description * The tree service factory, used internally by the umbTree and umbTreeItem directives */ -function treeService($q, treeResource, iconHelper, notificationsService, $rootScope, umbSessionStorage) { +function treeService($q, treeResource, iconHelper, notificationsService, $rootScope) { - //initialize the tree cache if nothing is there, we store the cache in sessionStorage which - // is applicable to the current open tab - if (!umbSessionStorage.get("treeCache")) { - umbSessionStorage.set("treeCache", {}); - } + //SD: Have looked at putting this in sessionStorage (not localStorage since that means you wouldn't be able to work + // in multiple tabs) - however our tree structure is cyclical, meaning a node has a reference to it's parent and it's children + // which you cannot serialize to sessionStorage. There's really no benefit of session storage except that you could refresh + // 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'; @@ -30,44 +31,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc return { - /** Internal method to return the tree cache - this also wires up the parent() function for each node since when we serialize to cache, functions are obviously lost */ - _getTreeCache: function(key) { - var cache = umbSessionStorage.get("treeCache"); - - //method to set the parent() delegate for each node - var setParent = function (currParent, currChildren) { - _.each(currChildren, function (child, index) { - //create the method, return it's parent - child.parent = function () { - return currParent; - }; - if (angular.isArray(child.children) && child.children.length > 0) { - //recurse - setParent(child, child.children); - } - - }); - }; - - //return the raw cache if a key is specified but there is nothing to process with that key - if (key && (!cache[key] || !cache[key].root || !angular.isArray(cache[key].root.children))) { - return cache; - } - else if (key && cache[key]) { - //if a key is specified only process that portion of the cache - setParent(cache[key].root, cache[key].root.children); - return cache; - } - else { - //no key is specified, process all of the cache - _.each(cache, function (val) { - if (val.root && angular.isArray(val.root.children)) { - setParent(val.root, val.root.children); - } - }); - return cache; - } - + /** Internal method to return the tree cache */ + _getTreeCache: function() { + return treeCache; }, /** 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 */ @@ -155,35 +121,18 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc return undefined; }, - /** This puts the tree into cache */ - cacheTree: function (cacheKey, section, tree) { - if (!cacheKey || !section || !tree) { - return; - } - - var key = getCacheKey({ cacheKey: cacheKey, section: section }); - //NOTE: we're not using the _getTreeCache method here because we don't want to process the parent() method, - // simply to get the raw values so we can update it. - var rawCache = umbSessionStorage.get("treeCache"); - rawCache[key] = tree; - umbSessionStorage.set("treeCache", rawCache); - }, - /** clears the tree cache - with optional cacheKey, optional section or optional filter */ clearCache: function (args) { //clear all if not specified if (!args) { - umbSessionStorage.set("treeCache", {}); + treeCache = {}; } else { - - var treeCache = this._getTreeCache(); - //if section and cache key specified just clear that cache if (args.section && args.cacheKey) { - var cacheKey = getCacheKey(args); + var cacheKey = getCacheKey(args); if (cacheKey && treeCache && treeCache[cacheKey] != null) { - umbSessionStorage.set("treeCache", _.omit(treeCache, cacheKey)); + treeCache = _.omit(treeCache, cacheKey); } } else if (args.childrenOf) { @@ -221,12 +170,12 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc if (result) { //set the result to the filtered data treeCache[args.cacheKey] = result; - umbSessionStorage.set("treeCache", treeCache); } else { //remove the cache - umbSessionStorage.set("treeCache", _.omit(treeCache, args.cacheKey)); + treeCache = _.omit(treeCache, args.cacheKey); } + } } @@ -236,7 +185,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc var toRemove1 = _.filter(allKeys1, function (k) { return k.startsWith(args.cacheKey + "_"); }); - umbSessionStorage.set("treeCache", _.omit(treeCache, toRemove1)); + treeCache = _.omit(treeCache, toRemove1); } else if (args.section) { //if only the section is specified then clear all cache regardless of cache key by that section @@ -244,7 +193,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc var toRemove2 = _.filter(allKeys2, function (k) { return k.endsWith("_" + args.section); }); - umbSessionStorage.set("treeCache", _.omit(treeCache, toRemove2)); + treeCache = _.omit(treeCache, toRemove2); } } }, @@ -398,8 +347,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc } var cacheKey = getCacheKey(args); - var treeCache = this._getTreeCache(cacheKey); - + //return the cache if it exists if (cacheKey && treeCache[cacheKey] !== undefined) { deferred.resolve(treeCache[cacheKey]); @@ -418,13 +366,12 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc //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); - ////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. - //if (cacheKey) { - // treeCache[cacheKey] = result; - // umbSessionStorage.set("treeCache", treeCache); - // deferred.resolve(treeCache[cacheKey]); - //} + //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. + if (cacheKey) { + treeCache[cacheKey] = result; + deferred.resolve(treeCache[cacheKey]); + } //return un-cached deferred.resolve(result); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 9f86c7f17c..7892726d80 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -17,7 +17,9 @@ function umbSessionStorage($window) { return { - get: function (key) { + get: function (key) { + console.log(storage); + console.log(storage["umb_" + key]); return angular.fromJson(storage["umb_" + key]); }, From 1f538d1bd56236b5087beebf5d9d968bf5d9cbda Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 15:24:08 +1100 Subject: [PATCH 09/14] Fixes: U4-3489 Member tree items should not have refresh node as an action --- src/Umbraco.Web/Trees/MemberTreeController.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index 6d4c2eb743..6ea9086cc3 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.Linq; using System.Net.Http.Formatting; using System.Web.Security; @@ -89,8 +90,16 @@ namespace Umbraco.Web.Trees return menu; } - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + Guid guid; + if (Guid.TryParse(id, out guid)) + { + menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); + } + else + { + menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); + menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + } return menu; } } From e7201b6a69701b7f7157d2fb6212c2709e8ad2e0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 16:07:53 +1100 Subject: [PATCH 10/14] Fixes: U4-3485 Can't use membership provider functions with a membertype that contains a "Belle" property editor --- .../BackwardsCompatibleData.cs | 25 ++++++++++---- .../PropertyEditors/PropertyValueEditor.cs | 2 ++ src/Umbraco.Core/XmlExtensions.cs | 14 ++------ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Tests/XmlExtensionsTests.cs | 33 +++++++++++++++++++ src/Umbraco.Tests/XmlHelperTests.cs | 1 - 6 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 src/Umbraco.Tests/XmlExtensionsTests.cs diff --git a/src/Umbraco.Core/PropertyEditors/BackwardsCompatibleData.cs b/src/Umbraco.Core/PropertyEditors/BackwardsCompatibleData.cs index e9f426c323..2d257a0d03 100644 --- a/src/Umbraco.Core/PropertyEditors/BackwardsCompatibleData.cs +++ b/src/Umbraco.Core/PropertyEditors/BackwardsCompatibleData.cs @@ -1,5 +1,6 @@ using System; using System.Xml; +using System.Xml.Linq; using Umbraco.Core.Models; using umbraco.interfaces; @@ -40,17 +41,29 @@ namespace Umbraco.Core.PropertyEditors Value = Value }; var xd = new XmlDocument(); - var xml = propertyEditor.ValueEditor.ConvertDbToXml(property); - xml.GetXmlNode(xd); + var xNode = propertyEditor.ValueEditor.ConvertDbToXml(property); + + //check if this xml fragment can be converted to an XmlNode + var xContainer = xNode as XContainer; + if (xContainer != null) + { + //imports to the document + xContainer.GetXmlNode(xd); + // return the XML node. + return data.ImportNode(xd.DocumentElement, true); + } - // return the XML node. - return data.ImportNode(xd.DocumentElement, true); + return ReturnCDataElement(data); } //if for some reason the prop editor wasn't found we'll default to returning the string value in a CDATA block. - var sValue = Value != null ? Value.ToString() : String.Empty; - return data.CreateCDataSection(sValue); + return ReturnCDataElement(data); + } + private XmlNode ReturnCDataElement(XmlDocument doc) + { + var sValue = Value != null ? Value.ToString() : string.Empty; + return doc.CreateCDataSection(sValue); } public void MakeNew(int PropertyId) diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs index e004779b60..0a2dccdead 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs @@ -289,6 +289,8 @@ namespace Umbraco.Core.PropertyEditors /// /// By default this will just return the value of ConvertDbToString but ensure that if the db value type is nvarchar or text /// it is a CDATA fragment, otherwise it is just a text fragment. + /// + /// This method by default will only return XText or XCData which must be wrapped in an element! /// public virtual XNode ConvertDbToXml(Property property) { diff --git a/src/Umbraco.Core/XmlExtensions.cs b/src/Umbraco.Core/XmlExtensions.cs index 25418a11de..d6a93af035 100644 --- a/src/Umbraco.Core/XmlExtensions.cs +++ b/src/Umbraco.Core/XmlExtensions.cs @@ -207,7 +207,7 @@ namespace Umbraco.Core /// /// /// - public static XmlNode ToXmlElement(this XElement xElement) + public static XmlNode ToXmlElement(this XContainer xElement) { var xmlDocument = new XmlDocument(); using (var xmlReader = xElement.CreateReader()) @@ -270,7 +270,7 @@ namespace Umbraco.Core return xDoc.Root; } - public static XmlNode GetXmlNode(this XElement element) + public static XmlNode GetXmlNode(this XContainer element) { using (XmlReader xmlReader = element.CreateReader()) { @@ -280,7 +280,7 @@ namespace Umbraco.Core } } - public static XmlNode GetXmlNode(this XElement element, XmlDocument xmlDoc) + public static XmlNode GetXmlNode(this XContainer element, XmlDocument xmlDoc) { using (XmlReader xmlReader = element.CreateReader()) { @@ -289,13 +289,5 @@ namespace Umbraco.Core } } - public static XmlNode GetXmlNode(this XNode element, XmlDocument xmlDoc) - { - using (XmlReader xmlReader = element.CreateReader()) - { - xmlDoc.Load(xmlReader); - return xmlDoc.DocumentElement; - } - } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4b571d51ba..a05a8b8f0f 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -478,6 +478,7 @@ + diff --git a/src/Umbraco.Tests/XmlExtensionsTests.cs b/src/Umbraco.Tests/XmlExtensionsTests.cs new file mode 100644 index 0000000000..8cd934effc --- /dev/null +++ b/src/Umbraco.Tests/XmlExtensionsTests.cs @@ -0,0 +1,33 @@ +using System.Xml; +using System.Xml.Linq; +using NUnit.Framework; +using Umbraco.Core; + +namespace Umbraco.Tests +{ + [TestFixture] + public class XmlExtensionsTests + { + [Test] + public void XCDataToXmlNode() + { + var cdata = new XElement("test", new XCData("hello world")); + var xdoc = new XmlDocument(); + + var xmlNode = cdata.GetXmlNode(xdoc); + + Assert.AreEqual(xmlNode.InnerText, "hello world"); + } + + [Test] + public void XTextToXmlNode() + { + var cdata = new XElement("test", new XText("hello world")); + var xdoc = new XmlDocument(); + + var xmlNode = cdata.GetXmlNode(xdoc); + + Assert.AreEqual(xmlNode.InnerText, "hello world"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/XmlHelperTests.cs b/src/Umbraco.Tests/XmlHelperTests.cs index ad00a364e6..0b7c8fd76b 100644 --- a/src/Umbraco.Tests/XmlHelperTests.cs +++ b/src/Umbraco.Tests/XmlHelperTests.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Linq; using System.Text; using System.Xml; -using System.Xml.Linq; using System.Xml.XPath; using NUnit.Framework; using Umbraco.Core; From c28f800fe6ed48ea766f5752a3241df7ce103426 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 17:38:09 +1100 Subject: [PATCH 11/14] Removes the re-committed app plugins property editor that shouldn't be there --- .../PropertyEditors/Views/simpleeditor.html | 29 ------------------- .../PropertyEditors/js/simpleeditor.js | 6 ---- .../App_Plugins/SimpleEditor/package.manifest | 14 --------- 3 files changed, 49 deletions(-) delete mode 100644 src/Umbraco.Web.UI/App_Plugins/SimpleEditor/PropertyEditors/Views/simpleeditor.html delete mode 100644 src/Umbraco.Web.UI/App_Plugins/SimpleEditor/PropertyEditors/js/simpleeditor.js delete mode 100644 src/Umbraco.Web.UI/App_Plugins/SimpleEditor/package.manifest diff --git a/src/Umbraco.Web.UI/App_Plugins/SimpleEditor/PropertyEditors/Views/simpleeditor.html b/src/Umbraco.Web.UI/App_Plugins/SimpleEditor/PropertyEditors/Views/simpleeditor.html deleted file mode 100644 index 5d050584fc..0000000000 --- a/src/Umbraco.Web.UI/App_Plugins/SimpleEditor/PropertyEditors/Views/simpleeditor.html +++ /dev/null @@ -1,29 +0,0 @@ -
- - -

{{content.name}}

-http://localhost/{{content.name}} -

{{tab.properties[1].value}}

-
- - diff --git a/src/Umbraco.Web.UI/App_Plugins/SimpleEditor/PropertyEditors/js/simpleeditor.js b/src/Umbraco.Web.UI/App_Plugins/SimpleEditor/PropertyEditors/js/simpleeditor.js deleted file mode 100644 index 343d6516af..0000000000 --- a/src/Umbraco.Web.UI/App_Plugins/SimpleEditor/PropertyEditors/js/simpleeditor.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - angular.module("umbraco").controller("Umbraco.Editors.SimpleEditorController", - function ($rootScope, $scope, notificationsService, dialogService) { - - }); -})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/SimpleEditor/package.manifest b/src/Umbraco.Web.UI/App_Plugins/SimpleEditor/package.manifest deleted file mode 100644 index 1c9b3158f2..0000000000 --- a/src/Umbraco.Web.UI/App_Plugins/SimpleEditor/package.manifest +++ /dev/null @@ -1,14 +0,0 @@ -{ - propertyEditors: [ - { - alias: "MyPackage.SimpleEditor", - name: "My Simple Editor", - editor: { - view: "~/App_Plugins/SimpleEditor/PropertyEditors/Views/simpleeditor.html" - } - } - ], - javascript: [ - '~/App_Plugins/SimpleEditor/PropertyEditors/Js/SimpleEditor.js' - ] -} \ No newline at end of file From a532b49485ab5db99fb33c415530962cb9fe2e5f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 17:38:32 +1100 Subject: [PATCH 12/14] Fixes: U4-3482 keepUserLoggedIn setting ignored? and streamlines some of the auth methods. --- .../UmbracoSettings/ContentElement.cs | 18 ++++++++++-- .../UmbracoSettings/SecurityElement.cs | 2 +- .../common/mocks/umbraco.servervariables.js | 3 +- .../src/common/services/user.service.js | 26 ++++++++++++++--- .../config/umbracoSettings.Release.config | 2 +- .../Editors/BackOfficeController.cs | 1 + src/Umbraco.Web/Security/WebSecurity.cs | 29 ++++--------------- src/Umbraco.Web/UmbracoModule.cs | 9 +++++- .../webservices/legacyAjaxCalls.asmx.cs | 2 ++ .../BasePages/BasePage.cs | 20 ++++++------- 10 files changed, 68 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 656251f3a0..7f90e55e57 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Configuration; namespace Umbraco.Core.Configuration.UmbracoSettings @@ -217,6 +218,19 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } + [Obsolete("This is here so that if this config element exists we won't get a YSOD, it is not used whatsoever and will be removed in future versions")] + [ConfigurationProperty("DocumentTypeIconList")] + internal InnerTextConfigurationElement DocumentTypeIconList + { + get + { + return new OptionalInnerTextConfigurationElement( + (InnerTextConfigurationElement)this["DocumentTypeIconList"], + //set the default + IconPickerBehaviour.HideFileDuplicates); + } + } + [ConfigurationProperty("disallowedUploadFiles")] internal CommaDelimitedConfigurationElement DisallowedUploadFiles { @@ -380,7 +394,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { get { return MacroErrors; } } - + IEnumerable IContentSection.DisallowedUploadFiles { get { return DisallowedUploadFiles; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs index 33a8a8584b..d3160a216b 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return new OptionalInnerTextConfigurationElement( (InnerTextConfigurationElement)this["keepUserLoggedIn"], //set the default - true); + false); } } 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 ac43f3488e..a4c31df259 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 @@ -23,7 +23,8 @@ Umbraco.Sys.ServerVariables = { umbracoSettings: { "umbracoPath": "/umbraco", "appPluginsPath" : "/App_Plugins", - "imageFileTypes": "jpeg,jpg,gif,bmp,png,tiff,tif" + "imageFileTypes": "jpeg,jpg,gif,bmp,png,tiff,tif", + "keepUserLoggedIn": true }, umbracoPlugins: { trees: [ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 213982b2ba..78a19e979c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -93,10 +93,28 @@ angular.module('umbraco.services') //we are either timed out or very close to timing out so we need to show the login dialog. //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function() { - userAuthExpired(); - }); - + if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { + angularHelper.safeApply($rootScope, function() { + userAuthExpired(); + }); + } + else { + //we've got less than 30 seconds remaining so let's check the server + + if (lastServerTimeoutSet != null) { + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; + //now go get it from the server + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + } + + //recurse the countdown! + countdownUserTimeout(); + + } } } }, 2000, //every 2 seconds diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 30526ee2a3..2ccc8a5077 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -52,7 +52,7 @@ - true + false false diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 40d03ecbb2..2ed33b2f91 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -172,6 +172,7 @@ namespace Umbraco.Web.Editors "imageFileTypes", string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes) }, + {"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn}, } }, { diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 657691dbe8..e4312ea994 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -173,6 +173,9 @@ namespace Umbraco.Web.Security _httpContext.UmbracoLogout(); } + /// + /// Renews the user's login ticket + /// public void RenewLoginTimeout() { _httpContext.RenewUmbracoAuthTicket(); @@ -357,18 +360,6 @@ namespace Umbraco.Web.Security return userApps.Any(uApp => uApp.InvariantEquals(app)); } - internal void UpdateLogin() - { - _httpContext.RenewUmbracoAuthTicket(); - } - - internal long GetTimeout() - { - var ticket = _httpContext.GetUmbracoAuthTicket(); - var ticks = ticket.Expiration.Ticks - DateTime.Now.Ticks; - return ticks; - } - /// /// Gets the user id. /// @@ -409,15 +400,8 @@ namespace Umbraco.Web.Security /// public bool ValidateCurrentUser() { - var ticket = _httpContext.GetUmbracoAuthTicket(); - if (ticket != null) - { - if (ticket.Expired == false) - { - return true; - } - } - return false; + var result = ValidateCurrentUser(false); + return result == ValidateRequestAttempt.Success; } /// @@ -440,8 +424,7 @@ namespace Umbraco.Web.Security { if (throwExceptions) throw new ArgumentException("You have no priviledges to the umbraco console. Please contact your administrator"); return ValidateRequestAttempt.FailedNoPrivileges; - } - UpdateLogin(); + } return ValidateRequestAttempt.Success; } if (throwExceptions) throw new ArgumentException("User has timed out!!"); diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 3ee5be98b4..f1cbc978db 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -225,10 +225,17 @@ namespace Umbraco.Web /// /// /// - /// We do not want to renew the ticket when we are checking for the user's remaining timeout. + /// We do not want to renew the ticket when we are checking for the user's remaining timeout unless - + /// UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn == true /// internal static bool ShouldIgnoreTicketRenew(Uri url, HttpContextBase httpContext) { + //this setting will renew the ticket for all requests. + if (UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn) + { + return false; + } + //initialize the ignore ticket urls - we don't need to lock this, it's concurrent and a hashset // we don't want to have to gen the url each request so this will speed things up a teeny bit. if (IgnoreTicketRenewUrls.Any() == false) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs index 551c859d8b..3ec59bd378 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs @@ -185,6 +185,7 @@ namespace umbraco.presentation.webservices return Application[helper.Request("key")].ToString(); } + [Obsolete("This is no longer used and will be removed in future versions")] [WebMethod] [ScriptMethod] public void RenewUmbracoSession() @@ -195,6 +196,7 @@ namespace umbraco.presentation.webservices } + [Obsolete("This is no longer used and will be removed in future versions")] [WebMethod] [ScriptMethod] public int GetSecondsBeforeUserLogout() diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index 73df8927e3..9c60a77fa1 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -7,6 +7,7 @@ using System.Web.Mvc; using System.Web.Routing; using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Cache; using Umbraco.Core.Logging; @@ -30,9 +31,7 @@ namespace umbraco.BasePages private User _user; private bool _userisValidated = false; private ClientTools _clientTools; - - private static readonly int UmbracoTimeOutInMinutes = GlobalSettings.TimeOutInMinutes; - + /// /// The path to the umbraco root folder /// @@ -135,6 +134,7 @@ namespace umbraco.BasePages ClientTools.RefreshAdmin(Seconds); } + //NOTE: This is basically replicated in WebSecurity because this class exists in a poorly placed assembly. - also why it is obsolete. private void ValidateUser() { var ticket = Context.GetUmbracoAuthTicket(); @@ -160,7 +160,7 @@ namespace umbraco.BasePages } else { - throw new InvalidOperationException("The user has no umbraco contextid - try logging in"); + throw new InvalidOperationException("The user has no umbraco contextid - try logging in"); } } @@ -205,13 +205,10 @@ namespace umbraco.BasePages /// public static bool ValidateCurrentUser() { - var ticket = HttpContext.Current.GetUmbracoAuthTicket(); - if (ticket != null) + var identity = HttpContext.Current.GetCurrentIdentity(true); + if (identity != null) { - if (ticket.Expired == false) - { - return true; - } + return true; } return false; } @@ -220,7 +217,8 @@ namespace umbraco.BasePages public static long GetTimeout(bool bypassCache) { var ticket = HttpContext.Current.GetUmbracoAuthTicket(); - var ticks = ticket.Expiration.Ticks - DateTime.Now.Ticks; + if (ticket.Expired) return 0; + var ticks = ticket.Expiration.Ticks - DateTime.Now.Ticks; return ticks; } From 5f5f535f05d29c2a742d47c6af5c8cae0011c577 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 18:07:10 +1100 Subject: [PATCH 13/14] Changes UserController to be CurrentUserController - and removes the other methods in there since that was an overlooked security issue. This controller is responsible solely for dealing with the currently logged in user. Changes over to be currentuser.resource as well. --- .../common/resources/currentuser.resource.js | 53 +++++ .../src/common/resources/user.resource.js | 113 ---------- .../dashboard/dashboard.tabs.controller.js | 6 +- .../Editors/BackOfficeController.cs | 4 +- ...Controller.cs => CurrentUserController.cs} | 199 ++++++++---------- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 6 files changed, 146 insertions(+), 231 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js delete mode 100644 src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js rename src/Umbraco.Web/Editors/{UserController.cs => CurrentUserController.cs} (79%) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js new file mode 100644 index 0000000000..2a814fb2f1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js @@ -0,0 +1,53 @@ +/** + * @ngdoc service + * @name umbraco.resources.currentUserResource + * @description Used for read/updates for the currently logged in user + * + * + **/ +function currentUserResource($q, $http, umbRequestHelper) { + + //the factory object returned + return { + + /** + * @ngdoc method + * @name umbraco.resources.currentUserResource#changePassword + * @methodOf umbraco.resources.currentUserResource + * + * @description + * Changes the current users password + * + * @returns {Promise} resourcePromise object containing the user array. + * + */ + changePassword: function (changePasswordArgs) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "currentUserApiBaseUrl", + "PostChangePassword"), + changePasswordArgs), + 'Failed to change password'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.currentUserResource#getMembershipProviderConfig + * @methodOf umbraco.resources.currentUserResource + * + * @description + * Gets the configuration of the user membership provider which is used to configure the change password form + */ + getMembershipProviderConfig: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "currentUserApiBaseUrl", + "GetMembershipProviderConfig")), + 'Failed to retreive membership provider config'); + }, + }; +} + +angular.module('umbraco.resources').factory('currentUserResource', currentUserResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js deleted file mode 100644 index 857ef65160..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * @ngdoc service - * @name umbraco.resources.userResource - * @description Retrives user data from the server, cannot be used for authentication, for this, use the user.service - * - * - **/ -function userResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - /** - * @ngdoc method - * @name umbraco.resources.userResource#getById - * @methodOf umbraco.resources.userResource - * - * @description - * Gets a user with a given id - * - * ##usage - *
-         * userResource.getById(1234)
-         *    .then(function(ent) {
-         *        var myUser = ent; 
-         *        alert('im here!');
-         *    });
-         * 
- * - * @param {Int} id id of user to return - * @returns {Promise} resourcePromise object containing the user. - * - */ - getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "userApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retreive user data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.userResource#getAll - * @methodOf umbraco.resources.userResource - * - * @description - * Gets all users available on the system - * - * ##usage - *
-         * contentResource.getAll()
-         *    .then(function(userArray) {
-         *        var myUsers = userArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @returns {Promise} resourcePromise object containing the user array. - * - */ - getAll: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "userApiBaseUrl", - "GetAll")), - 'Failed to retreive all users'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.userResource#changePassword - * @methodOf umbraco.resources.userResource - * - * @description - * Changes the current users password - * - * @returns {Promise} resourcePromise object containing the user array. - * - */ - changePassword: function (changePasswordArgs) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "userApiBaseUrl", - "PostChangePassword"), - changePasswordArgs), - 'Failed to change password'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.userResource#getMembershipProviderConfig - * @methodOf umbraco.resources.userResource - * - * @description - * Gets the configuration of the user membership provider which is used to configure the change password form - */ - getMembershipProviderConfig: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "userApiBaseUrl", - "GetMembershipProviderConfig")), - 'Failed to retreive membership provider config'); - }, - }; -} - -angular.module('umbraco.resources').factory('userResource', userResource); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index 4e0426f620..496ed9e47f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -80,7 +80,7 @@ function MediaFolderBrowserDashboardController($rootScope, $scope, assetsService angular.module("umbraco").controller("Umbraco.Dashboard.MediaFolderBrowserDashboardController", MediaFolderBrowserDashboardController); -function ChangePasswordDashboardController($scope, xmlhelper, $log, userResource, formHelper) { +function ChangePasswordDashboardController($scope, xmlhelper, $log, currentUserResource, formHelper) { //create the initial model for change password property editor $scope.changePasswordModel = { @@ -91,7 +91,7 @@ function ChangePasswordDashboardController($scope, xmlhelper, $log, userResource }; //go get the config for the membership provider and add it to the model - userResource.getMembershipProviderConfig().then(function(data) { + currentUserResource.getMembershipProviderConfig().then(function(data) { $scope.changePasswordModel.config = data; //ensure the hasPassword config option is set to true (the user of course has a password already assigned) //this will ensure the oldPassword is shown so they can change it @@ -105,7 +105,7 @@ function ChangePasswordDashboardController($scope, xmlhelper, $log, userResource $scope.changePassword = function() { if (formHelper.submitForm({ scope: $scope })) { - userResource.changePassword($scope.changePasswordModel.value).then(function(data) { + currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) { //if the password has been reset, then update our model if (data.value) { diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 2ed33b2f91..1e36107689 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -102,8 +102,8 @@ namespace Umbraco.Web.Editors controller => controller.PostLogin(null, null)) }, { - "userApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAll()) + "currentUserApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetMembershipProviderConfig()) }, { "legacyApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web/Editors/UserController.cs b/src/Umbraco.Web/Editors/CurrentUserController.cs similarity index 79% rename from src/Umbraco.Web/Editors/UserController.cs rename to src/Umbraco.Web/Editors/CurrentUserController.cs index bea40acc81..880ca7ab41 100644 --- a/src/Umbraco.Web/Editors/UserController.cs +++ b/src/Umbraco.Web/Editors/CurrentUserController.cs @@ -1,112 +1,87 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using System.Web.Http; -using System.Web.Security; -using AutoMapper; -using Umbraco.Core.Configuration; -using Umbraco.Web.Models; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; -using Umbraco.Web.Mvc; -using Umbraco.Web.UI; -using Umbraco.Web.WebApi; -using umbraco; -using legacyUser = umbraco.BusinessLogic.User; -using System.Net.Http; -using System.Collections.Specialized; -using Constants = Umbraco.Core.Constants; - - -namespace Umbraco.Web.Editors -{ - /// - /// Controller to back the User.Resource service, used for fetching user data when already authenticated. user.service is currently used for handling authentication - /// - [PluginController("UmbracoApi")] - public class UserController : UmbracoAuthorizedJsonController - { - - /// - /// Returns the configuration for the backoffice user membership provider - used to configure the change password dialog - /// - /// - public IDictionary GetMembershipProviderConfig() - { - var provider = Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider]; - if (provider == null) - { - throw new InvalidOperationException("No back office membership provider found with the name " + UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider); - } - return provider.GetConfiguration(); - } - - /// - /// Returns a user by id - /// - /// - /// - public UserDetail GetById(int id) - { - var user = Services.UserService.GetUserById(id); - if (user == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - - return Mapper.Map(user); - } - - /// - /// Changes the users password - /// - /// - /// - /// If the password is being reset it will return the newly reset password, otherwise will return an empty value - /// - public ModelWithNotifications PostChangePassword(ChangingPasswordModel data) - { - var userProvider = Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider]; - if (userProvider == null) - { - throw new InvalidOperationException("No membership provider found with the name " + UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider); - } - - //TODO: WE need to support this! - requires UI updates, etc... - if (userProvider.RequiresQuestionAndAnswer) - { - throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified"); - } - - var passwordChangeResult = Security.ChangePassword(Security.CurrentUser.Username, data, userProvider); - if (passwordChangeResult.Success) - { - //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword - var result = new ModelWithNotifications(passwordChangeResult.Result.ResetPassword); - result.AddSuccessNotification(ui.Text("user", "password"), ui.Text("user", "passwordChanged")); - return result; - } - - //it wasn't successful, so add the change error to the model state, we've name the property alias _umb_password on the form - // so that is why it is being used here. - ModelState.AddPropertyError( - passwordChangeResult.Result.ChangeError, - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - - throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); - } - - /// - /// Returns all active users - /// - /// - public IEnumerable GetAll() - { - return Services.UserService.GetAllUsers().Where(x => x.IsLockedOut == false).Select(Mapper.Map); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Security; +using AutoMapper; +using Umbraco.Core.Configuration; +using Umbraco.Web.Models; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Mvc; +using Umbraco.Web.UI; +using Umbraco.Web.WebApi; +using umbraco; +using legacyUser = umbraco.BusinessLogic.User; +using System.Net.Http; +using System.Collections.Specialized; +using Constants = Umbraco.Core.Constants; + + +namespace Umbraco.Web.Editors +{ + /// + /// Controller to back the User.Resource service, used for fetching user data when already authenticated. user.service is currently used for handling authentication + /// + [PluginController("UmbracoApi")] + public class CurrentUserController : UmbracoAuthorizedJsonController + { + + /// + /// Returns the configuration for the backoffice user membership provider - used to configure the change password dialog + /// + /// + public IDictionary GetMembershipProviderConfig() + { + var provider = Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider]; + if (provider == null) + { + throw new InvalidOperationException("No back office membership provider found with the name " + UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider); + } + return provider.GetConfiguration(); + } + + /// + /// Changes the users password + /// + /// + /// + /// If the password is being reset it will return the newly reset password, otherwise will return an empty value + /// + public ModelWithNotifications PostChangePassword(ChangingPasswordModel data) + { + var userProvider = Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider]; + if (userProvider == null) + { + throw new InvalidOperationException("No membership provider found with the name " + UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider); + } + + //TODO: WE need to support this! - requires UI updates, etc... + if (userProvider.RequiresQuestionAndAnswer) + { + throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified"); + } + + var passwordChangeResult = Security.ChangePassword(Security.CurrentUser.Username, data, userProvider); + if (passwordChangeResult.Success) + { + //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword + var result = new ModelWithNotifications(passwordChangeResult.Result.ResetPassword); + result.AddSuccessNotification(ui.Text("user", "password"), ui.Text("user", "passwordChanged")); + return result; + } + + //it wasn't successful, so add the change error to the model state, we've name the property alias _umb_password on the form + // so that is why it is being used here. + ModelState.AddPropertyError( + passwordChangeResult.Result.ChangeError, + string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a9173caa86..563d415509 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -362,7 +362,7 @@ - + From df867af9009fea7ee2854179da6d424952c6f090 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2013 18:13:00 +1100 Subject: [PATCH 14/14] Fixes: U4-3329 Link to "Edit Profile" errors if you don't have access to Users --- .../src/views/common/dialogs/user.controller.js | 1 + .../src/views/common/dialogs/user.html | 4 ++-- src/Umbraco.Web/Models/ContentEditing/UserDetail.cs | 9 ++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js index 8f440c641d..cad419bbfd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js @@ -36,6 +36,7 @@ angular.module("umbraco") $scope.user = user; if ($scope.user) { $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds; + $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1; //set the timer updateTimeout(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html index 488e982719..53ba8b8f69 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html @@ -19,8 +19,8 @@