From 69f805eccae2dbf945de8477ae3979e455312c2a Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Thu, 14 Nov 2013 10:38:56 +0100 Subject: [PATCH 01/22] Proof that CreateIndexBuilder.NonClustered() does not affect index creation statement on SQL Server. CreateIndexBuilder_SqlServer_NonClustered_CreatesNonClusteredIndex() fails expecting NONCLUSTERED but getting CLUSTERED. CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex() fails expecting UNIQUE NONCLUSTERED but getting CLUSTERED. --- .../SyntaxProvider/SqlSyntaxProviderTests.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlSyntaxProviderTests.cs index 3909a17847..cc94620476 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlSyntaxProviderTests.cs @@ -1,7 +1,11 @@ using System; +using System.Collections.Generic; using NUnit.Framework; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Migrations.Syntax.Create.Index; +using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.SyntaxProvider @@ -39,6 +43,71 @@ namespace Umbraco.Tests.Persistence.SyntaxProvider } } + [Test] + public void Format_SqlServer_NonClusteredIndexDefinition_AddsNonClusteredDirective() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + + var indexDefinition = CreateIndexDefinition(); + indexDefinition.IndexType = IndexTypes.NonClustered; + + var actual = SqlSyntaxContext.SqlSyntaxProvider.Format(indexDefinition); + Assert.AreEqual("CREATE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", actual); + } + + [Test] + public void Format_SqlServer_NonClusteredIndexDefinition_UsingIsClusteredFalse_AddsClusteredDirective() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + + var indexDefinition = CreateIndexDefinition(); + indexDefinition.IsClustered = false; + + var actual = SqlSyntaxContext.SqlSyntaxProvider.Format(indexDefinition); + Assert.AreEqual("CREATE CLUSTERED INDEX [IX_A] ON [TheTable] ([A])", actual); + } + + [Test] + public void CreateIndexBuilder_SqlServer_NonClustered_CreatesNonClusteredIndex() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + var createExpression = new CreateIndexExpression { Index = { Name = "IX_A" } }; + var builder = new CreateIndexBuilder(createExpression); + builder.OnTable("TheTable").OnColumn("A").Ascending().WithOptions().NonClustered(); + Assert.AreEqual("CREATE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); + } + + [Test] + public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + var createExpression = new CreateIndexExpression { Index = { Name = "IX_A" } }; + var builder = new CreateIndexBuilder(createExpression); + builder.OnTable("TheTable").OnColumn("A").Ascending().WithOptions().Unique(); + Assert.AreEqual("CREATE UNIQUE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); + } + + [Test] + public void CreateIndexBuilder_SqlServer_Clustered_CreatesClusteredIndex() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + var createExpression = new CreateIndexExpression { Index = { Name = "IX_A" } }; + var builder = new CreateIndexBuilder(createExpression); + builder.OnTable("TheTable").OnColumn("A").Ascending().WithOptions().Clustered(); + Assert.AreEqual("CREATE CLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); + } + + private static IndexDefinition CreateIndexDefinition() + { + return new IndexDefinition + { + ColumnName = "A", + Name = "IX_A", + TableName = "TheTable", + SchemaName = "dbo" + }; + } + [TearDown] public void TearDown() { From e3cd85a6a2dab797c82e0bb3bb4185f82e4823f7 Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Thu, 14 Nov 2013 10:55:35 +0100 Subject: [PATCH 02/22] Made CreateIndexBuilder set IndexType in Clustered(), NonClustered() and Unique(). --- .../Migrations/Syntax/Create/Index/CreateIndexBuilder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/CreateIndexBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/CreateIndexBuilder.cs index 405e013545..d4c28800e5 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/CreateIndexBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/CreateIndexBuilder.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Index @@ -53,18 +54,21 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Index public ICreateIndexOnColumnSyntax NonClustered() { + Expression.Index.IndexType = IndexTypes.NonClustered; Expression.Index.IsClustered = false; return this; } public ICreateIndexOnColumnSyntax Clustered() { + Expression.Index.IndexType = IndexTypes.Clustered; Expression.Index.IsClustered = true; return this; } ICreateIndexOnColumnSyntax ICreateIndexOptionsSyntax.Unique() { + Expression.Index.IndexType = IndexTypes.UniqueNonClustered; Expression.Index.IsUnique = true; return this; } From 393c365b6e21fad6189d97be1c8cfad29faae0ee Mon Sep 17 00:00:00 2001 From: Richard Soeteman Date: Thu, 14 Nov 2013 14:48:04 +0100 Subject: [PATCH 03/22] Fix for U4-3529 Ysod editing Partial View --- src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs b/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs index 4fa9146dbc..5c6db74513 100644 --- a/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs +++ b/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs @@ -137,7 +137,7 @@ namespace Umbraco.Web.UI.Controls private bool DoesMacroHaveParameters(int macroId) { - return ApplicationContext.DatabaseContext.Database.ExecuteScalar(string.Format("select 1 from cmsMacroProperty where macro = {0}", macroId)) == 1; + return ApplicationContext.DatabaseContext.Database.ExecuteScalar(string.Format("SELECT COUNT(*) from cmsMacroProperty where macro = {0}", macroId)) > 0; } } } From 1c742db414115de8e100fc43454ffc66e055a6c4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 09:43:21 +1100 Subject: [PATCH 04/22] Cleans up a bit more code - no longer passing in nav controller scope, removes zany args.scope assignment back to the nav ctrl via the nav service, this is not necessary at all and doesn't make any sense. --- .../src/common/services/navigation.service.js | 47 ++++++++----------- .../src/views/common/navigation.controller.js | 2 +- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index c619f37b92..79cf1a75b7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -176,15 +176,11 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setGlobalState("showTray", false); }, - //adding this to get clean global access to the main tree directive - //there will only ever be one main tree event handler - //we need to pass in the current scope for binding these actions - - //TODO: How many places are we assigning a currentNode?? Now we're assigning a currentNode arbitrarily to this - // scope - which looks to be the scope of the navigation controller - but then we are assigning a global current - // node on the ui object?? This is a mess. - - setupTreeEvents: function(treeEventHandler, scope) { + /** + Called to assign the main tree event handler - this is called by the navigation controller. + TODO: Potentially another dev could call this which would kind of mung the whole app so potentially there's a better way. + */ + setupTreeEvents: function(treeEventHandler) { mainTreeEventHandler = treeEventHandler; //when a tree is loaded into a section, we need to put it into appState @@ -199,12 +195,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo //set the current selected node appState.setTreeState("selectedNode", args.node); } - - //TODO: what the heck is going on here? - this seems really zany, allowing us to modify the - // navigationController.scope from within the navigationService to assign back to the args - // so that we can change the navigationController.scope from within the umbTree directive. Hrm. - args.scope = scope; - }); //this reacts to the options item in the tree @@ -215,11 +205,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo //Set the current action node (this is not the same as the current selected node!) appState.setMenuState("currentNode", args.node); - //TODO: what the heck is going on here? - this seems really zany, allowing us to modify the - // navigationController.scope from within the navigationService to assign back to the args - // so that we can change the navigationController.scope from within the umbTree directive. Hrm. - args.scope = scope; - if (args.event && args.event.altKey) { args.skipDefault = true; } @@ -231,11 +216,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo ev.stopPropagation(); ev.preventDefault(); - //TODO: what the heck is going on here? - this seems really zany, allowing us to modify the - // navigationController.scope from within the navigationService to assign back to the args - // so that we can change the navigationController.scope from within the umbTree directive. Hrm. - args.scope = scope; - args.skipDefault = true; service.showMenu(ev, args); }); @@ -457,8 +437,19 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo setMode("tree"); }, + /** Executes a given menu action */ executeMenuAction: function (action, node, section) { + if (!action) { + throw "action cannot be null"; + } + if (!node) { + throw "node cannot be null"; + } + if (!section) { + throw "section cannot be null"; + } + if (action.metaData && action.metaData["jsAction"] && angular.isString(action.metaData["jsAction"])) { //we'll try to get the jsAction from the injector @@ -471,12 +462,12 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo eval(js); } else { - var _service = $injector.get(menuAction[0]); - if (!_service) { + var menuActionService = $injector.get(menuAction[0]); + if (!menuActionService) { throw "The angular service " + menuAction[0] + " could not be found"; } - var method = _service[menuAction[1]]; + var method = menuActionService[menuAction[1]]; if (!method) { throw "The method " + menuAction[1] + " on the angular service " + menuAction[0] + " could not be found"; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js index 095ec8c3d8..435f6d844e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js @@ -14,7 +14,7 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams, //TODO: Need to think about this and an nicer way to acheive what this is doing. //the tree event handler i used to subscribe to the main tree click events $scope.treeEventHandler = $({}); - navigationService.setupTreeEvents($scope.treeEventHandler, $scope); + navigationService.setupTreeEvents($scope.treeEventHandler); //Put the navigation service on this scope so we can use it's methods/properties in the view. // IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since From 5314afb51afac0ada946daf3e7ec264783d86fae Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 10:38:36 +1100 Subject: [PATCH 05/22] Changed editorContext to use appState instead for content/media/member editors - the appState saved is the basic entity, this means that if property editors resolve this value they do not have access to actually change the real content model which we don't want to allow (or changing other property's values). Fixed up some inconsistencies between all of these editors and the content editor helper, fixed up tree syncing with the members editor. --- .../src/common/services/appstate.service.js | 3 +- .../services/contenteditinghelper.service.js | 25 +++++++------- .../common/services/editorContext.service.js | 2 -- .../views/content/content.edit.controller.js | 23 ++++++++----- .../datatype/datatype.edit.controller.js | 10 +++--- .../src/views/media/media.edit.controller.js | 18 +++++++--- .../views/member/member.edit.controller.js | 34 ++++++++++++++----- 7 files changed, 72 insertions(+), 43 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/common/services/editorContext.service.js diff --git a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js index f0563d5900..81e3fff441 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js @@ -16,7 +16,8 @@ function appState($rootScope) { touchDevice: null, showTray: null, stickyNavigation: null, - navMode: null + navMode: null, + editingEntity: null }; var sectionState = { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 0a2a74d006..5ed9212d3c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -2,11 +2,13 @@ /** * @ngdoc service * @name umbraco.services.contentEditingHelper -* @description A helper service for content/media/member controllers when editing/creating/saving content. +* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by +* all editors to share logic and reduce the amount of replicated code among editors. **/ -function contentEditingHelper($location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper) { +function contentEditingHelper($location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper, appState) { return { + /** * @ngdoc method @@ -36,15 +38,15 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser * @function * * @description - * re-binds all changed property values to the origContent object from the newContent object and returns an array of changed properties. + * re-binds all changed property values to the origContent object from the savedContent object and returns an array of changed properties. */ - reBindChangedProperties: function (origContent, newContent) { + reBindChangedProperties: function (origContent, savedContent) { var changed = []; //get a list of properties since they are contained in tabs var allOrigProps = this.getAllProps(origContent); - var allNewProps = this.getAllProps(newContent); + var allNewProps = this.getAllProps(savedContent); function getNewProp(alias) { return _.find(allNewProps, function (item) { @@ -66,8 +68,8 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser continue; } - if (!_.isEqual(origContent[o], newContent[o])) { - origContent[o] = newContent[o]; + if (!_.isEqual(origContent[o], savedContent[o])) { + origContent[o] = savedContent[o]; } } @@ -109,9 +111,6 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser if (!args.err) { throw "args.err cannot be null"; } - if (!args.allNewProps && !angular.isArray(args.allNewProps)) { - throw "args.allNewProps must be a valid array"; - } if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) { throw "args.redirectOnFailure must be set to true or false"; } @@ -168,11 +167,11 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser if (!args) { throw "args cannot be null"; } - if (!args.newContent) { - throw "args.newContent cannot be null"; + if (!args.savedContent) { + throw "args.savedContent cannot be null"; } - if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.newContent.id)) { + if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { //we are not redirecting because this is not new content, it is existing content. In this case // we need to detect what properties have changed and re-bind them with the server data. diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorContext.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorContext.service.js deleted file mode 100644 index 4f69a3011a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorContext.service.js +++ /dev/null @@ -1,2 +0,0 @@ -angular.module("umbraco.services") - .value('editorContext', undefined); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index f47b98f5a5..487ba611db 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the content editor */ -function ContentEditController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, editorContext, treeService, fileManager, formHelper, umbRequestHelper, keyboardService) { +function ContentEditController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper) { //setup scope vars $scope.defaultButton = null; @@ -15,9 +15,6 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS $scope.currentSection = appState.getSectionState("currentSection"); $scope.currentNode = null; //the editors affiliated node - //we need this to share our content object with property editors - editorContext = $scope.content; - //This sets up the action buttons based on what permissions the user has. //The allowedActions parameter contains a list of chars, each represents a button by permission so //here we'll build the buttons according to the chars of the user. @@ -123,11 +120,15 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS contentEditingHelper.handleSuccessfulSave({ scope: $scope, - newContent: data, + savedContent: data, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) }); + //update appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); + configureButtons(data); + navigationService.syncTree({ tree: "content", path: data.path.split(","), forceReload: true }).then(function (syncArgs) { $scope.currentNode = syncArgs.node; }); @@ -139,10 +140,12 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS contentEditingHelper.handleSaveError({ redirectOnFailure: true, err: err, - allNewProps: contentEditingHelper.getAllProps(err.data), - allOrigProps: contentEditingHelper.getAllProps($scope.content) + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); + //update appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); + deferred.reject(err); }); } @@ -159,6 +162,8 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS .then(function(data) { $scope.loaded = true; $scope.content = data; + //put this into appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); configureButtons($scope.content); }); } @@ -168,6 +173,8 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS .then(function(data) { $scope.loaded = true; $scope.content = data; + //put this into appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); configureButtons($scope.content); //in one particular special case, after we've created a new item we redirect back to the edit @@ -194,7 +201,7 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS contentEditingHelper.handleSuccessfulSave({ scope: $scope, - newContent: data, + savedContent: data, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) }); diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js index 5a83003c46..7f82dd8fec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js @@ -45,8 +45,8 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig $scope.preValues = []; if ($routeParams.create) { - //we are creating so get an empty content item - dataTypeResource.getScaffold($routeParams.id, $routeParams.doctype) + //we are creating so get an empty data type item + dataTypeResource.getScaffold($routeParams.id) .then(function(data) { $scope.loaded = true; $scope.preValuesLoaded = true; @@ -101,7 +101,7 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig contentEditingHelper.handleSuccessfulSave({ scope: $scope, - newContent: data, + savedContent: data, rebindCallback: function() { createPreValueProps(data.preValues); } @@ -117,9 +117,7 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig // to be the same thing since that only really matters for content/media. contentEditingHelper.handleSaveError({ redirectOnFailure: false, - err: err, - allNewProps: $scope.preValues, - allOrigProps: $scope.preValues + err: err }); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 99f21c3fb9..1d3e226fd8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the media editor */ -function mediaEditController($scope, $routeParams, appState, mediaResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper) { +function mediaEditController($scope, $routeParams, appState, mediaResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper, umbModelMapper) { //setup scope vars $scope.nav = navigationService; @@ -19,7 +19,8 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, navi .then(function (data) { $scope.loaded = true; $scope.content = data; - + //put this into appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); }); } else { @@ -27,7 +28,9 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, navi .then(function (data) { $scope.loaded = true; $scope.content = data; - + //put this into appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); + //in one particular special case, after we've created a new item we redirect back to the edit // route but there might be server validation errors in the collection which we need to display // after the redirect, so we will bind all subscriptions which will show the server validation errors @@ -52,10 +55,13 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, navi contentEditingHelper.handleSuccessfulSave({ scope: $scope, - newContent: data, + savedContent: data, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) }); + //update appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); + navigationService.syncTree({ tree: "media", path: data.path, forceReload: true }).then(function (syncArgs) { $scope.currentNode = syncArgs.node; }); @@ -65,9 +71,11 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, navi contentEditingHelper.handleSaveError({ err: err, redirectOnFailure: true, - allNewProps: contentEditingHelper.getAllProps(err.data), rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); + + //update appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index f1ade8b374..4a20db9c43 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -6,19 +6,29 @@ * @description * The controller for the member editor */ -function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, treeService) { +function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper) { //setup scope vars $scope.nav = navigationService; $scope.currentSection = appState.getSectionState("currentSection"); $scope.currentNode = null; //the editors affiliated node + //build a path to sync the tree with + function buildTreePath(data) { + //TODO: Will this work for the 'other' list ? + var path = data.name[0] + "," + data.key; + path = path.replace(/-/g, ''); + return path; + } + if ($routeParams.create) { //we are creating so get an empty member item memberResource.getScaffold($routeParams.doctype) .then(function(data) { $scope.loaded = true; $scope.content = data; + //put this into appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); }); } else { @@ -40,9 +50,10 @@ function MemberEditController($scope, $routeParams, $location, $q, $window, appS $scope.loaded = true; $scope.content = data; - //build a path to sync the tree with - var path = data.name[0]+"," + data.key; - path = path.replace(/-/g,''); + //put this into appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); + + var path = buildTreePath(data); navigationService.syncTree({ tree: "member", path: path.split(",") }).then(function (syncArgs) { $scope.currentNode = syncArgs.node; @@ -69,13 +80,18 @@ function MemberEditController($scope, $routeParams, $location, $q, $window, appS contentEditingHelper.handleSuccessfulSave({ scope: $scope, - newContent: data, + savedContent: data, //specify a custom id to redirect to since we want to use the GUID redirectId: data.key, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) }); - navigationService.syncTree({ tree: "member", path: path.split(",") }).then(function (syncArgs) { + //update appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); + + var path = buildTreePath(data); + + navigationService.syncTree({ tree: "member", path: path.split(","), forceReload: true }).then(function (syncArgs) { $scope.currentNode = syncArgs.node; }); @@ -84,9 +100,11 @@ function MemberEditController($scope, $routeParams, $location, $q, $window, appS contentEditingHelper.handleSaveError({ redirectOnFailure: false, err: err, - allNewProps: contentEditingHelper.getAllProps(err.data), - allOrigProps: contentEditingHelper.getAllProps($scope.content) + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); + + //update appState + appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content)); }); } From c61fd1ec27263030757b3998369149092848e7d4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 12:26:29 +1100 Subject: [PATCH 06/22] Fixes datetimepicker once and for all, fixes user model to return the culture not just the language, adds ability to localize the datetimepicker - will default to english but is easy to add other versions, currently the only custom one in there is brazilian (since it was already made). fixes: U4-2863 Date picker-textbox jumps between 12 and 24 hours clock U4-3537 Cannot clear date/time value, still have problems with it's null validation not clearing from the server --- .../bootstrap-datetimepicker.js | 0 .../bootstrap-datetimepicker.min.css | 14 +-- .../bootstrap-datetimepicker.min.js | 0 .../langs/datetimepicker.pt-BR.js | 14 +++ src/Umbraco.Web.UI.Client/src/less/hacks.less | 11 ++ .../datepicker/datepicker.controller.js | 103 ++++++++++-------- .../datepicker/datepicker.html | 6 +- .../Models/ContentEditing/UserDetail.cs | 2 +- .../Models/Mapping/UserModelMapper.cs | 2 + .../PropertyEditors/DateTimePropertyEditor.cs | 4 +- 10 files changed, 99 insertions(+), 57 deletions(-) rename src/Umbraco.Web.UI.Client/{src/views/propertyeditors/datepicker => lib/datetimepicker}/bootstrap-datetimepicker.js (100%) rename src/Umbraco.Web.UI.Client/{src/views/propertyeditors/datepicker => lib/datetimepicker}/bootstrap-datetimepicker.min.css (99%) rename src/Umbraco.Web.UI.Client/{src/views/propertyeditors/datepicker => lib/datetimepicker}/bootstrap-datetimepicker.min.js (100%) create mode 100644 src/Umbraco.Web.UI.Client/lib/datetimepicker/langs/datetimepicker.pt-BR.js diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/bootstrap-datetimepicker.js b/src/Umbraco.Web.UI.Client/lib/datetimepicker/bootstrap-datetimepicker.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/bootstrap-datetimepicker.js rename to src/Umbraco.Web.UI.Client/lib/datetimepicker/bootstrap-datetimepicker.js diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/bootstrap-datetimepicker.min.css b/src/Umbraco.Web.UI.Client/lib/datetimepicker/bootstrap-datetimepicker.min.css similarity index 99% rename from src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/bootstrap-datetimepicker.min.css rename to src/Umbraco.Web.UI.Client/lib/datetimepicker/bootstrap-datetimepicker.min.css index 37449fc396..36394e276b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/bootstrap-datetimepicker.min.css +++ b/src/Umbraco.Web.UI.Client/lib/datetimepicker/bootstrap-datetimepicker.min.css @@ -1,8 +1,8 @@ -/*! - * Datepicker for Bootstrap - * - * Copyright 2012 Stefan Petre - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * +/*! + * Datepicker for Bootstrap + * + * Copyright 2012 Stefan Petre + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-datetimepicker-widget{top:0;left:0;width:250px;padding:4px;margin-top:1px;z-index:3000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.bootstrap-datetimepicker-widget:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);position:absolute;top:-7px;left:6px}.bootstrap-datetimepicker-widget:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:7px}.bootstrap-datetimepicker-widget.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget>ul{list-style-type:none;margin:0}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:100%;font-weight:bold;font-size:1.2em}.bootstrap-datetimepicker-widget table[data-hour-format="12"] .separator{width:4px;padding:0;margin:0}.bootstrap-datetimepicker-widget .datepicker>div{display:none}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget td,.bootstrap-datetimepicker-widget th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.bootstrap-datetimepicker-widget td.day:hover,.bootstrap-datetimepicker-widget td.hour:hover,.bootstrap-datetimepicker-widget td.minute:hover,.bootstrap-datetimepicker-widget td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget td.old,.bootstrap-datetimepicker-widget td.new{color:#999}.bootstrap-datetimepicker-widget td.active,.bootstrap-datetimepicker-widget td.active:hover{color:#fff;background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#04c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget td.active:hover,.bootstrap-datetimepicker-widget td.active:hover:hover,.bootstrap-datetimepicker-widget td.active:active,.bootstrap-datetimepicker-widget td.active:hover:active,.bootstrap-datetimepicker-widget td.active.active,.bootstrap-datetimepicker-widget td.active:hover.active,.bootstrap-datetimepicker-widget td.active.disabled,.bootstrap-datetimepicker-widget td.active:hover.disabled,.bootstrap-datetimepicker-widget td.active[disabled],.bootstrap-datetimepicker-widget td.active:hover[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.bootstrap-datetimepicker-widget td.active:active,.bootstrap-datetimepicker-widget td.active:hover:active,.bootstrap-datetimepicker-widget td.active.active,.bootstrap-datetimepicker-widget td.active:hover.active{background-color:#039 \9}.bootstrap-datetimepicker-widget td.disabled,.bootstrap-datetimepicker-widget td.disabled:hover{background:0;color:#999;cursor:not-allowed}.bootstrap-datetimepicker-widget td span{display:block;width:47px;height:54px;line-height:54px;float:left;margin:2px;cursor:pointer;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.bootstrap-datetimepicker-widget td span:hover{background:#eee}.bootstrap-datetimepicker-widget td span.active{color:#fff;background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#04c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget td span.active:hover,.bootstrap-datetimepicker-widget td span.active:active,.bootstrap-datetimepicker-widget td span.active.active,.bootstrap-datetimepicker-widget td span.active.disabled,.bootstrap-datetimepicker-widget td span.active[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.bootstrap-datetimepicker-widget td span.active:active,.bootstrap-datetimepicker-widget td span.active.active{background-color:#039 \9}.bootstrap-datetimepicker-widget td span.old{color:#999}.bootstrap-datetimepicker-widget td span.disabled,.bootstrap-datetimepicker-widget td span.disabled:hover{background:0;color:#999;cursor:not-allowed}.bootstrap-datetimepicker-widget th.switch{width:145px}.bootstrap-datetimepicker-widget th.next,.bootstrap-datetimepicker-widget th.prev{font-size:21px}.bootstrap-datetimepicker-widget th.disabled,.bootstrap-datetimepicker-widget th.disabled:hover{background:0;color:#999;cursor:not-allowed}.bootstrap-datetimepicker-widget thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget thead tr:first-child th:hover{background:#eee}.input-append.date .add-on i,.input-prepend.date .add-on i{display:block;cursor:pointer;width:16px;height:16px}.bootstrap-datetimepicker-widget.left-oriented:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.left-oriented:after{left:auto;right:7px} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/bootstrap-datetimepicker.min.js b/src/Umbraco.Web.UI.Client/lib/datetimepicker/bootstrap-datetimepicker.min.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/bootstrap-datetimepicker.min.js rename to src/Umbraco.Web.UI.Client/lib/datetimepicker/bootstrap-datetimepicker.min.js diff --git a/src/Umbraco.Web.UI.Client/lib/datetimepicker/langs/datetimepicker.pt-BR.js b/src/Umbraco.Web.UI.Client/lib/datetimepicker/langs/datetimepicker.pt-BR.js new file mode 100644 index 0000000000..2455b93da6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/datetimepicker/langs/datetimepicker.pt-BR.js @@ -0,0 +1,14 @@ +/** + * Brazilian translation for bootstrap-datetimepicker + * Cauan Cabral + */ +; (function ($) { + $.fn.datetimepicker.dates['pt-BR'] = { + days: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"], + daysShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"], + daysMin: ["Do", "Se", "Te", "Qu", "Qu", "Se", "Sa", "Do"], + months: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"], + monthsShort: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"], + today: "Hoje" + }; +}(jQuery)); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index b4d5280db1..cd11c1ed69 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -70,4 +70,15 @@ iframe, .content-column-body { .legacy-custom-file{ width: 16px; height: 16px; margin-right: 11px; display: inline-block; background-position: center center; +} + +/* + missing icon names in helveticons that are in font-awesome - used by the datepicker, + basically making them equivalent to their helviton icon +*/ +.icon-chevron-up:before { + content: "\e128"; +} +.icon-chevron-down:before { + content: "\e0c9"; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js index 111b03bff1..b8f12ac7b7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js @@ -1,54 +1,67 @@ -angular.module("umbraco").controller("Umbraco.Editors.DatepickerController", - function ($scope, notificationsService, assetsService, userService) { +function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService) { - function applyDate(e){ - // when a date is changed, update the model - if (e.localDate) { - if ($scope.model.config.format == "yyyy-MM-dd HH:mm:ss") { - $scope.$apply(function(){ - $scope.model.value = e.localDate.toIsoDateTimeString(); - }); - }else{ - $scope.model.value = e.localDate.toIsoDateString(); - } + //lists the custom language files that we currently support + var customLangs = ["pt-BR"]; + + //setup the default config + var config = { + pickDate: true, + pickTime: true, + pick12HourFormat: false, + format: "yyyy-MM-dd hh:mm:ss" + }; + + //map the user config + $scope.model.config = angular.extend(config, $scope.model.config); + + //handles the date changing via the api + function applyDate(e) { + angularHelper.safeApply($scope, function() { + // when a date is changed, update the model + if (e.localDate) { + if ($scope.model.config.format == "yyyy-MM-dd hh:mm:ss") { + $scope.model.value = e.localDate.toIsoDateTimeString(); } - } + else { + $scope.model.value = e.localDate.toIsoDateString(); + } + } + }); + } - function initEditor(){ - // Get the id of the datepicker button that was clicked - var pickerId = $scope.model.alias; - // Open the datepicker and add a changeDate eventlistener - - $("#" + pickerId) - .datetimepicker($scope.model.config) - .on("changeDate", applyDate) - .on("hide", applyDate); + //get the current user to see if we can localize this picker + userService.getCurrentUser().then(function (user) { + + var filesToLoad = ["lib/datetimepicker/bootstrap-datetimepicker.min.js"]; + + //if we support this custom culture, set it, then we'll need to load in that lang file + if (_.contains(customLangs, user.locale)) { + $scope.model.config.language = user.locale; + filesToLoad.push("lib/datetimepicker/langs/datetimepicker." + user.locale + ".js"); } - userService.getCurrentUser().then(function(user){ + assetsService.load(filesToLoad).then( + function() { + //The Datepicker js and css files are available and all components are ready to use. - //setup the default config - var config = { - pickDate: true, - pickTime: true, - language: user.locale, - format: "yyyy-MM-dd HH:mm:ss" - }; + // Get the id of the datepicker button that was clicked + var pickerId = $scope.model.alias; + // Open the datepicker and add a changeDate eventlistener + $("#datepicker" + pickerId) + //.datetimepicker(config); + .datetimepicker($scope.model.config) + .on("changeDate", applyDate); - //format:"yyyy-MM-dd HH:mm:ss" + //now assign the date + $("#datepicker" + pickerId).val($scope.model.value); - //map the user config - angular.extend(config, $scope.model.config); - //map back to the model - $scope.model.config = config; - $scope.model.viewvalue = $scope.model.value; + }); - assetsService.loadJs( - 'views/propertyeditors/datepicker/bootstrap-datetimepicker.js' - ).then(initEditor); - }); - - assetsService.loadCss( - 'views/propertyeditors/datepicker/bootstrap-datetimepicker.min.css' - ); - }); \ No newline at end of file + }); + + assetsService.loadCss( + 'views/propertyeditors/datepicker/bootstrap-datetimepicker.min.css' + ); +} + +angular.module("umbraco").controller("Umbraco.PropertyEditors.DatepickerController", dateTimePickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index d0f8ce915f..c5ddf7e967 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -1,7 +1,7 @@ -
-
+
+
diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs index 32b890d9fd..1ab322e3f3 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "locale", IsRequired = true)] [Required] - public string Language { get; set; } + public string Culture { get; set; } /// /// The MD5 lowercase hash of the email which can be used by gravatar diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 0648bba260..a3a3fa1a80 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -4,6 +4,7 @@ using Umbraco.Core; using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; +using umbraco; namespace Umbraco.Web.Models.Mapping { @@ -14,6 +15,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) .ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserType.Alias)) + .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => ui.Culture(user))) .ForMember( detail => detail.EmailHash, opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())); diff --git a/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs index 5e91323585..ee416604d8 100644 --- a/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs @@ -12,7 +12,9 @@ namespace Umbraco.Web.PropertyEditors { _defaultPreVals = new Dictionary { - {"format", "yyyy-MM-dd HH:mm:ss"} + //NOTE: This is very important that we do not use .Net format's there, this format + // is the correct format for the JS picker we are using so you cannot capitalize the HH, they need to be 'hh' + {"format", "yyyy-MM-dd hh:mm:ss"} }; } From f1d837dfd137a4c26759c89afa49d7b9062411c8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 12:46:55 +1100 Subject: [PATCH 07/22] fixes: U4-3539 Cannot set a scheduled publish at date/time gives a warning message --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 5 +++++ src/Umbraco.Web/Editors/ContentController.cs | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index c45c0d9170..1c7f930e53 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -676,6 +676,11 @@ To manage your website, simply open the umbraco back office and start adding con If you just want to setup simple protection using a single login and password + + + diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 6b3fdb1649..c7f04e6ac4 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -594,10 +594,21 @@ namespace Umbraco.Web.Editors display.AddWarningNotification( ui.Text("publish"), ui.Text("speechBubbles", "contentPublishedFailedByEvent")); + break; + case PublishStatusType.FailedAwaitingRelease: + display.AddWarningNotification( + ui.Text("publish"), + ui.Text("publish", "contentPublishedFailedAwaitingRelease", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) + }, + UmbracoUser).Trim()); break; case PublishStatusType.FailedHasExpired: - case PublishStatusType.FailedAwaitingRelease: + //TODO: We should add proper error messaging for this! case PublishStatusType.FailedIsTrashed: + //TODO: We should add proper error messaging for this! case PublishStatusType.FailedContentInvalid: display.AddWarningNotification( ui.Text("publish"), From 89481405b520cd22d241c8f4dfea14e6e1d06ba6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 12:57:28 +1100 Subject: [PATCH 08/22] Fixes: U4-3534 Sorting content items --- .../umbraco_client/Dialogs/SortDialog.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js b/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js index 75a84292c3..a7bb1d9f29 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js @@ -43,18 +43,17 @@ }, _saveSort: function() { - var rows = jQuery('#sortableNodes tbody tr'); + var rows = $('#sortableNodes tbody tr'); var sortOrder = ""; $.each(rows, function () { sortOrder += $(this).attr("id").replace("node_", "") + ","; }); - document.getElementById("sortingDone").style.display = 'none'; - document.getElementById("sortArea").style.display = 'none'; - - document.getElementById("loading").style.display = 'block'; - + $("#sortingDone").hide(); + $("#sortArea").hide(); + $("#loading").show(); + var self = this; $.ajax({ @@ -69,9 +68,10 @@ }); }, - _showConfirm: function() { - document.getElementById("loading").style.display = 'none'; - document.getElementById("sortingDone").style.display = 'block'; + _showConfirm: function () { + $(".umb-dialog-footer").hide(); + $("#loading").hide(); + $("#sortingDone").show(); UmbClientMgr.mainTree().reloadActionNode(); }, @@ -101,7 +101,7 @@ }); //setup the drag/drop sorting - $("#sortableNodes").tableDnD({ containment: jQuery("#sortableFrame") }); + $("#sortableNodes").tableDnD({ containment: $("#sortableFrame") }); //wire up the submit button self._opts.submitButton.click(function() { From eca823ea900ba3650f2f38d2201026196debdbc3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 13:49:20 +1100 Subject: [PATCH 09/22] Fix for U4-3529 Ysod editing Partial View --- src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs b/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs index 4fa9146dbc..5c6db74513 100644 --- a/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs +++ b/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs @@ -137,7 +137,7 @@ namespace Umbraco.Web.UI.Controls private bool DoesMacroHaveParameters(int macroId) { - return ApplicationContext.DatabaseContext.Database.ExecuteScalar(string.Format("select 1 from cmsMacroProperty where macro = {0}", macroId)) == 1; + return ApplicationContext.DatabaseContext.Database.ExecuteScalar(string.Format("SELECT COUNT(*) from cmsMacroProperty where macro = {0}", macroId)) > 0; } } } From 34cebe989f093065fa03c45754a647ae1f7782ac Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 14:29:34 +1100 Subject: [PATCH 10/22] Fixes up the EntityRepository to use dynamics for media as well so that *all* fields in the main table are returned so we can store this information in AdditionalData, now that this is working nicely we should make the entity repo lookup the properties for content too just like we're doing for media since these get put into AdditionalData as well and there's really not much overhead to do that. --- src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 1 + .../Factories/UmbracoEntityFactory.cs | 26 +++- .../Repositories/EntityRepository.cs | 133 ++++++++++-------- 3 files changed, 100 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index e04e20ff0a..c9967c0688 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -61,6 +61,7 @@ namespace Umbraco.Core.Models [FriendlyName("Member Group")] MemberGroup, + //TODO: What is a 'Content Item' supposed to be??? /// /// Content Item /// diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index ec3f48a0ca..e42397e11c 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -1,13 +1,29 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Reflection; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Repositories; namespace Umbraco.Core.Persistence.Factories { internal class UmbracoEntityFactory : IEntityFactory { + internal void AddAdditionalData(UmbracoEntity entity, IDictionary originalEntityProperties) + { + var entityProps = TypeHelper.GetPublicProperties(typeof(IUmbracoEntity)).Select(x => x.Name).ToArray(); + + //figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data + foreach (var k in originalEntityProperties.Keys + .Select(x => new { orig = x, title = x.ConvertCase(StringAliasCaseType.PascalCase) }) + .Where(x => entityProps.InvariantContains(x.title) == false)) + { + entity.AdditionalData[k.title] = originalEntityProperties[k.orig]; + } + } + internal UmbracoEntity BuildEntityFromDynamic(dynamic d) { var entity = new UmbracoEntity(d.trashed) @@ -38,12 +54,18 @@ namespace Umbraco.Core.Persistence.Factories Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion); } var newestVersion = default(Guid); - Guid.TryParse(d.newestVersion.ToString(), out newestVersion); + if (d.newestVersion != null) + { + Guid.TryParse(d.newestVersion.ToString(), out newestVersion); + } entity.IsPublished = publishedVersion != default(Guid) || (newestVersion != default(Guid) && publishedVersion == newestVersion); entity.IsDraft = newestVersion != default(Guid) && (publishedVersion == default(Guid) || publishedVersion != newestVersion); entity.HasPendingChanges = (publishedVersion != default(Guid) && newestVersion != default(Guid)) && publishedVersion != newestVersion; - + + //Now we can assign the additional data! + AddAdditionalData(entity, asDictionary); + return entity; } diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 0fd0fc4e76..7336e36ab7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -52,12 +52,12 @@ namespace Umbraco.Core.Persistence.Repositories public IUmbracoEntity GetByKey(Guid key) { var sql = GetBaseWhere(GetBase, false, false, key); - var nodeDto = _work.Database.FirstOrDefault(sql); + var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntity(nodeDto); + var entity = factory.BuildEntityFromDynamic(nodeDto); return entity; } @@ -67,12 +67,12 @@ namespace Umbraco.Core.Persistence.Repositories bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key).Append(GetGroupBy(isContent, isMedia)); - var nodeDto = _work.Database.FirstOrDefault(sql); + var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntity(nodeDto); + var entity = factory.BuildEntityFromDynamic(nodeDto); return entity; } @@ -80,12 +80,12 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IUmbracoEntity Get(int id) { var sql = GetBaseWhere(GetBase, false, false, id); - var nodeDto = _work.Database.FirstOrDefault(sql); + var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntity(nodeDto); + var entity = factory.BuildEntityFromDynamic(nodeDto); return entity; } @@ -95,12 +95,13 @@ namespace Umbraco.Core.Persistence.Repositories bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id).Append(GetGroupBy(isContent, isMedia)); - var nodeDto = _work.Database.FirstOrDefault(sql); + + var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntity(nodeDto); + var entity = factory.BuildEntityFromDynamic(nodeDto); return entity; } @@ -119,16 +120,27 @@ namespace Umbraco.Core.Persistence.Repositories bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var sql = GetBaseWhere(GetBase, isContent, isMedia, string.Empty, objectTypeId).Append(GetGroupBy(isContent, isMedia)); - var dtos = isMedia - ? _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql) - : _work.Database.Fetch(sql); + var factory = new UmbracoEntityFactory(); - foreach (var dto in dtos) + if (isMedia) { - var entity = factory.BuildEntity(dto); - yield return entity; + //for now treat media differently + //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + var entities = _work.Database.Fetch( + new UmbracoEntityRelator().Map, sql); + foreach (var entity in entities) + { + yield return entity; + } + } + else + { + var dtos = _work.Database.Fetch(sql); + foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto))) + { + yield return entity; + } } } } @@ -140,10 +152,10 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate().Append(GetGroupBy(false, false)); - var dtos = _work.Database.Fetch(sql); + var dtos = _work.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); - var list = dtos.Select(factory.BuildEntity).Cast().ToList(); + var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); return list; } @@ -162,42 +174,22 @@ namespace Umbraco.Core.Persistence.Repositories if (isMedia) { - //treat media differently for now - var dtos = _work.Database.Fetch( + //treat media differently for now + //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + var entities = _work.Database.Fetch( new UmbracoEntityRelator().Map, sql); - return dtos.Select(factory.BuildEntity).Cast().ToArray(); + return entities; } else { - //use dynamic so that we can get ALL properties from the SQL - //we'll have to stitch stuff together manually but we can get our - //additional data to put in the dictionary. + //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData var dtos = _work.Database.Fetch(sql); - var entityProps = TypeHelper.GetPublicProperties(typeof (IUmbracoEntity)).Select(x => x.Name).ToArray(); - var result = new List(); - foreach (var d in dtos) - { - //build the initial entity - IUmbracoEntity entity = factory.BuildEntityFromDynamic(d); - - //convert the dynamic row to dictionary - var asDictionary = (IDictionary) d; - - //figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data - foreach (var k in asDictionary.Keys - .Select(x => new {orig = x, title = x.ConvertCase(StringAliasCaseType.PascalCase)}) - .Where(x => entityProps.InvariantContains(x.title) == false)) - { - entity.AdditionalData[k.title] = asDictionary[k.orig]; - } - - result.Add(entity); - } - return result; + return dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); } } #endregion + #region Sql Statements @@ -396,11 +388,20 @@ namespace Umbraco.Core.Persistence.Repositories public string UmbracoFile { get; set; } } + /// + /// This is a special relator in that it is not returning a DTO but a real resolved entity and that it accepts + /// a dynamic instance. + /// + /// + /// We're doing this because when we query the db, we want to use dynamic so that it returns all available fields not just the ones + /// defined on the entity so we can them to additional data + /// internal class UmbracoEntityRelator { - internal UmbracoEntityDto Current; + internal UmbracoEntity Current; + private readonly UmbracoEntityFactory _factory = new UmbracoEntityFactory(); - internal UmbracoEntityDto Map(UmbracoEntityDto a, UmbracoPropertyDto p) + internal UmbracoEntity Map(dynamic a, UmbracoPropertyDto p) { // Terminating call. Since we can return null from this function // we need to be ready for PetaPoco to callback later with null @@ -408,28 +409,44 @@ namespace Umbraco.Core.Persistence.Repositories if (a == null) return Current; - // Is this the same UmbracoEntityDto as the current one we're processing - if (Current != null && Current.UniqueId == a.UniqueId) + // Is this the same UmbracoEntity as the current one we're processing + if (Current != null && Current.Key == a.uniqueID) { - // Yes, just add this UmbracoPropertyDto to the current UmbracoEntityDto's collection - Current.UmbracoPropertyDtos.Add(p); - - // Return null to indicate we're not done with this UmbracoEntityDto yet + // Yes, just add this UmbracoProperty to the current UmbracoEntity's collection + if (Current.UmbracoProperties == null) + { + Current.UmbracoProperties = new List(); + } + Current.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty + { + PropertyEditorAlias = p.PropertyEditorAlias, + Value = p.UmbracoFile + }); + // Return null to indicate we're not done with this UmbracoEntity yet return null; } - // This is a different UmbracoEntityDto to the current one, or this is the + // This is a different UmbracoEntity to the current one, or this is the // first time through and we don't have a Tab yet // Save the current UmbracoEntityDto var prev = Current; - // Setup the new current UmbracoEntityDto - Current = a; - Current.UmbracoPropertyDtos = new List(); - Current.UmbracoPropertyDtos.Add(p); + // Setup the new current UmbracoEntity + + Current = _factory.BuildEntityFromDynamic(a); - // Return the now populated previous UmbracoEntityDto (or null if first time through) + //add the property/create the prop list if null + Current.UmbracoProperties = new List + { + new UmbracoEntity.UmbracoProperty + { + PropertyEditorAlias = p.PropertyEditorAlias, + Value = p.UmbracoFile + } + }; + + // Return the now populated previous UmbracoEntity (or null if first time through) return prev; } } From b4c005c1408d12d9078a1a1587a8003d95fd3a75 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 14:53:26 +1100 Subject: [PATCH 11/22] Fixes up the EntityRepository to use dynamics for media as well so that *all* fields in the main table are returned so we can store this information in AdditionalData, now that this is working nicely we should make the entity repo lookup the properties for content too just like we're doing for media since these get put into AdditionalData as well and there's really not much overhead to do that. Conflicts: src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs --- src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 1 + .../Factories/UmbracoEntityFactory.cs | 36 +++- .../Repositories/EntityRepository.cs | 185 ++++++++++++------ 3 files changed, 153 insertions(+), 69 deletions(-) diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index e04e20ff0a..c9967c0688 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -61,6 +61,7 @@ namespace Umbraco.Core.Models [FriendlyName("Member Group")] MemberGroup, + //TODO: What is a 'Content Item' supposed to be??? /// /// Content Item /// diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 226294be0f..cdbab3d0c1 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -1,15 +1,33 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Reflection; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Repositories; namespace Umbraco.Core.Persistence.Factories { internal class UmbracoEntityFactory : IEntityFactory { + internal void AddAdditionalData(UmbracoEntity entity, IDictionary originalEntityProperties) + { + var entityProps = TypeHelper.GetPublicProperties(typeof(IUmbracoEntity)).Select(x => x.Name).ToArray(); + + //figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data + foreach (var k in originalEntityProperties.Keys + .Select(x => new { orig = x, title = x.ConvertCase(StringAliasCaseType.PascalCase) }) + .Where(x => entityProps.InvariantContains(x.title) == false)) + { + entity.AdditionalData[k.title] = originalEntityProperties[k.orig]; + } + } + internal UmbracoEntity BuildEntityFromDynamic(dynamic d) { + var asDictionary = (IDictionary)d; + var entity = new UmbracoEntity(d.trashed) { CreateDate = d.createDate, @@ -23,14 +41,12 @@ namespace Umbraco.Core.Persistence.Factories Path = d.path, SortOrder = d.sortOrder, HasChildren = d.children > 0, - ContentTypeAlias = d.alias ?? string.Empty, - ContentTypeIcon = d.icon ?? string.Empty, - ContentTypeThumbnail = d.thumbnail ?? string.Empty, + ContentTypeAlias = asDictionary.ContainsKey("alias") ? (d.alias ?? string.Empty) : string.Empty, + ContentTypeIcon = asDictionary.ContainsKey("icon") ? (d.icon ?? string.Empty) : string.Empty, + ContentTypeThumbnail = asDictionary.ContainsKey("thumbnail") ? (d.thumbnail ?? string.Empty) : string.Empty, UmbracoProperties = new List() }; - var asDictionary = (IDictionary)d; - var publishedVersion = default(Guid); //some content items don't have a published version if (asDictionary.ContainsKey("publishedVersion") && asDictionary["publishedVersion"] != null) @@ -38,12 +54,18 @@ namespace Umbraco.Core.Persistence.Factories Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion); } var newestVersion = default(Guid); - Guid.TryParse(d.newestVersion.ToString(), out newestVersion); + if (asDictionary.ContainsKey("newestVersion") && d.newestVersion != null) + { + Guid.TryParse(d.newestVersion.ToString(), out newestVersion); + } entity.IsPublished = publishedVersion != default(Guid) || (newestVersion != default(Guid) && publishedVersion == newestVersion); entity.IsDraft = newestVersion != default(Guid) && (publishedVersion == default(Guid) || publishedVersion != newestVersion); entity.HasPendingChanges = (publishedVersion != default(Guid) && newestVersion != default(Guid)) && publishedVersion != newestVersion; - + + //Now we can assign the additional data! + AddAdditionalData(entity, asDictionary); + return entity; } diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index bbfea4082a..c935d1626c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -49,15 +49,43 @@ namespace Umbraco.Core.Persistence.Repositories #region Query Methods - public virtual IUmbracoEntity Get(int id) + public IUmbracoEntity GetByKey(Guid key) { - var sql = GetBaseWhere(GetBase, false, false, id); - var nodeDto = _work.Database.FirstOrDefault(sql); + var sql = GetBaseWhere(GetBase, false, false, key); + var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntity(nodeDto); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; + } + + public IUmbracoEntity GetByKey(Guid key, Guid objectTypeId) + { + bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key).Append(GetGroupBy(isContent, isMedia)); + var nodeDto = _work.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; + + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; + } + + public virtual IUmbracoEntity Get(int id) + { + var sql = GetBaseWhere(GetBase, false, false, id); + var nodeDto = _work.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; + + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); return entity; } @@ -67,12 +95,13 @@ namespace Umbraco.Core.Persistence.Repositories bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id).Append(GetGroupBy(isContent, isMedia)); - var nodeDto = _work.Database.FirstOrDefault(sql); + + var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntity(nodeDto); + var entity = factory.BuildEntityFromDynamic(nodeDto); return entity; } @@ -91,16 +120,27 @@ namespace Umbraco.Core.Persistence.Repositories bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var sql = GetBaseWhere(GetBase, isContent, isMedia, string.Empty, objectTypeId).Append(GetGroupBy(isContent, isMedia)); - var dtos = isMedia - ? _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql) - : _work.Database.Fetch(sql); + var factory = new UmbracoEntityFactory(); - foreach (var dto in dtos) + if (isMedia) { - var entity = factory.BuildEntity(dto); - yield return entity; + //for now treat media differently + //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + var entities = _work.Database.Fetch( + new UmbracoEntityRelator().Map, sql); + foreach (var entity in entities) + { + yield return entity; + } + } + else + { + var dtos = _work.Database.Fetch(sql); + foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto))) + { + yield return entity; + } } } } @@ -112,10 +152,10 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate().Append(GetGroupBy(false, false)); - var dtos = _work.Database.Fetch(sql); + var dtos = _work.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); - var list = dtos.Select(factory.BuildEntity).Cast().ToList(); + var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); return list; } @@ -134,42 +174,22 @@ namespace Umbraco.Core.Persistence.Repositories if (isMedia) { - //treat media differently for now - var dtos = _work.Database.Fetch( + //treat media differently for now + //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + var entities = _work.Database.Fetch( new UmbracoEntityRelator().Map, sql); - return dtos.Select(factory.BuildEntity).Cast().ToArray(); + return entities; } else { - //use dynamic so that we can get ALL properties from the SQL - //we'll have to stitch stuff together manually but we can get our - //additional data to put in the dictionary. + //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData var dtos = _work.Database.Fetch(sql); - var entityProps = TypeHelper.GetPublicProperties(typeof (IUmbracoEntity)).Select(x => x.Name).ToArray(); - var result = new List(); - foreach (var d in dtos) - { - //build the initial entity - IUmbracoEntity entity = factory.BuildEntityFromDynamic(d); - - //convert the dynamic row to dictionary - var asDictionary = (IDictionary) d; - - //figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data - foreach (var k in asDictionary.Keys - .Select(x => new {orig = x, title = x.ConvertCase(StringAliasCaseType.PascalCase)}) - .Where(x => entityProps.InvariantContains(x.title) == false)) - { - entity.AdditionalData[k.title] = asDictionary[k.orig]; - } - - result.Add(entity); - } - return result; + return dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); } } #endregion + #region Sql Statements @@ -239,26 +259,42 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, string additionWhereStatement, Guid id) + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, string additionWhereStatement, Guid nodeObjectType) { var sql = baseQuery(isContent, isMedia, additionWhereStatement) - .Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = id }); + .Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType }); return sql; } protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, int id) { - var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '"+ id +"'") + var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'") .Where("umbracoNode.id = @Id", new { Id = id }) .Append(GetGroupBy(isContent, isMedia)); return sql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid objectId, int id) + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid key) { - var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '"+ id +"'") + var sql = baseQuery(isContent, isMedia, " AND umbracoNode.uniqueID = '" + key + "'") + .Where("umbracoNode.uniqueID = @UniqueID", new { UniqueID = key }) + .Append(GetGroupBy(isContent, isMedia)); + return sql; + } + + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id) + { + var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'") .Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType", - new {Id = id, NodeObjectType = objectId}); + new { Id = id, NodeObjectType = nodeObjectType }); + return sql; + } + + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key) + { + var sql = baseQuery(isContent, isMedia, " AND umbracoNode.uniqueID = '" + key + "'") + .Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType", + new { UniqueID = key, NodeObjectType = nodeObjectType }); return sql; } @@ -352,11 +388,20 @@ namespace Umbraco.Core.Persistence.Repositories public string UmbracoFile { get; set; } } + /// + /// This is a special relator in that it is not returning a DTO but a real resolved entity and that it accepts + /// a dynamic instance. + /// + /// + /// We're doing this because when we query the db, we want to use dynamic so that it returns all available fields not just the ones + /// defined on the entity so we can them to additional data + /// internal class UmbracoEntityRelator { - internal UmbracoEntityDto Current; + internal UmbracoEntity Current; + private readonly UmbracoEntityFactory _factory = new UmbracoEntityFactory(); - internal UmbracoEntityDto Map(UmbracoEntityDto a, UmbracoPropertyDto p) + internal UmbracoEntity Map(dynamic a, UmbracoPropertyDto p) { // Terminating call. Since we can return null from this function // we need to be ready for PetaPoco to callback later with null @@ -364,28 +409,44 @@ namespace Umbraco.Core.Persistence.Repositories if (a == null) return Current; - // Is this the same UmbracoEntityDto as the current one we're processing - if (Current != null && Current.UniqueId == a.UniqueId) + // Is this the same UmbracoEntity as the current one we're processing + if (Current != null && Current.Key == a.uniqueID) { - // Yes, just add this UmbracoPropertyDto to the current UmbracoEntityDto's collection - Current.UmbracoPropertyDtos.Add(p); - - // Return null to indicate we're not done with this UmbracoEntityDto yet + // Yes, just add this UmbracoProperty to the current UmbracoEntity's collection + if (Current.UmbracoProperties == null) + { + Current.UmbracoProperties = new List(); + } + Current.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty + { + DataTypeControlId = p.DataTypeControlId, + Value = p.UmbracoFile + }); + // Return null to indicate we're not done with this UmbracoEntity yet return null; } - // This is a different UmbracoEntityDto to the current one, or this is the + // This is a different UmbracoEntity to the current one, or this is the // first time through and we don't have a Tab yet // Save the current UmbracoEntityDto var prev = Current; - // Setup the new current UmbracoEntityDto - Current = a; - Current.UmbracoPropertyDtos = new List(); - Current.UmbracoPropertyDtos.Add(p); + // Setup the new current UmbracoEntity + + Current = _factory.BuildEntityFromDynamic(a); - // Return the now populated previous UmbracoEntityDto (or null if first time through) + //add the property/create the prop list if null + Current.UmbracoProperties = new List + { + new UmbracoEntity.UmbracoProperty + { + DataTypeControlId = p.DataTypeControlId, + Value = p.UmbracoFile + } + }; + + // Return the now populated previous UmbracoEntity (or null if first time through) return prev; } } From 4a6103ba94005b825c83eaca8b72397f5a511568 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 15:10:02 +1100 Subject: [PATCH 12/22] Fixes some merge issues, tries to speed up unit tests a little. --- .../Repositories/EntityRepository.cs | 15 +++-------- .../TestHelpers/BaseUmbracoApplicationTest.cs | 25 +++++++++++++------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 2eea8da8e7..7336e36ab7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -142,15 +142,6 @@ namespace Umbraco.Core.Persistence.Repositories yield return entity; } } - else - { - var dtos = _work.Database.Fetch(sql); - foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto))) - { - yield return entity; - } - } - } } } @@ -277,7 +268,7 @@ namespace Umbraco.Core.Persistence.Repositories protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, int id) { - var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'") + var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '"+ id +"'") .Where("umbracoNode.id = @Id", new { Id = id }) .Append(GetGroupBy(isContent, isMedia)); return sql; @@ -293,9 +284,9 @@ namespace Umbraco.Core.Persistence.Repositories protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id) { - var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'") + var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '"+ id +"'") .Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType", - new { Id = id, NodeObjectType = nodeObjectType }); + new {Id = id, NodeObjectType = nodeObjectType}); return sql; } diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 403a285209..490ab33751 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -20,6 +20,9 @@ namespace Umbraco.Tests.TestHelpers [TestFixture] public abstract class BaseUmbracoApplicationTest : BaseUmbracoConfigurationTest { + private static bool _mappersInitialized = false; + private static readonly object Locker = new object(); + [SetUp] public override void Initialize() { @@ -57,18 +60,24 @@ namespace Umbraco.Tests.TestHelpers private void InitializeMappers() { - Mapper.Initialize(configuration => + lock (Locker) { - var mappers = PluginManager.Current.FindAndCreateInstances(); - foreach (var mapper in mappers) + //only need to initialize one time + if (_mappersInitialized == false) { - mapper.ConfigureMappings(configuration, ApplicationContext); - } - }); + _mappersInitialized = true; + Mapper.Initialize(configuration => + { + var mappers = PluginManager.Current.FindAndCreateInstances(); + foreach (var mapper in mappers) + { + mapper.ConfigureMappings(configuration, ApplicationContext); + } + }); + } + } } - - /// /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan /// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs From 9774744991308748d7b53d56274ee7caa0fed830 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 16:00:48 +1100 Subject: [PATCH 13/22] should knock off a few minutes for running tests. --- .../Services/EntityServiceTests.cs | 40 +++++++++++------ .../TestHelpers/BaseDatabaseFactoryTest.cs | 44 ++++++++++++------- .../TestHelpers/BaseUmbracoApplicationTest.cs | 26 ++++++----- .../BaseUmbracoConfigurationTest.cs | 23 +++++++--- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 36 +++++++-------- src/Umbraco.Tests/unit-test-log4net.config | 9 +++- 6 files changed, 112 insertions(+), 66 deletions(-) diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index c6bf01245a..cc2316c349 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Services @@ -25,6 +26,11 @@ namespace Umbraco.Tests.Services base.TearDown(); } + protected override DatabaseBehavior DatabaseTestBehavior + { + get { return DatabaseBehavior.NewSchemaPerFixture; } + } + [Test] public void EntityService_Can_Find_All_Content_By_UmbracoObjectTypes() { @@ -136,24 +142,32 @@ namespace Umbraco.Tests.Services y => y.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)), Is.True); } + private static bool _isSetup = false; + public override void CreateTestData() { - base.CreateTestData(); + if (_isSetup == false) + { + _isSetup = true; - //Create and Save folder-Media -> 1050 - var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); - var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); - ServiceContext.MediaService.Save(folder, 0); + base.CreateTestData(); - //Create and Save image-Media -> 1051 - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); - ServiceContext.MediaService.Save(image, 0); + //Create and Save folder-Media -> 1050 + var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder, 0); - //Create and Save file-Media -> 1052 - var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); - var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); - ServiceContext.MediaService.Save(file, 0); + //Create and Save image-Media -> 1051 + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); + ServiceContext.MediaService.Save(image, 0); + + //Create and Save file-Media -> 1052 + var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); + var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); + ServiceContext.MediaService.Save(file, 0); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index c0bc7a5c79..7c918008c3 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -55,10 +55,10 @@ namespace Umbraco.Tests.TestHelpers public override void Initialize() { InitializeFirstRunFlags(); - + var path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", path); - + //disable cache var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); @@ -67,25 +67,35 @@ namespace Umbraco.Tests.TestHelpers GetDbProviderName()); _appContext = new ApplicationContext( - //assign the db context - new DatabaseContext(dbFactory), - //assign the service context - new ServiceContext(new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper), - cacheHelper) - { - IsReady = true - }; + //assign the db context + new DatabaseContext(dbFactory), + //assign the service context + new ServiceContext(new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper), + cacheHelper) + { + IsReady = true + }; base.Initialize(); - DatabaseContext.Initialize(dbFactory.ProviderName, dbFactory.ConnectionString); + using (DisposableTimer.TraceDuration("init")) + { + using (DisposableTimer.TraceDuration("DatabaseContext.Initialize")) + { + DatabaseContext.Initialize(dbFactory.ProviderName, dbFactory.ConnectionString); + } + using (DisposableTimer.TraceDuration("CreateSqlCeDatabase")) + { + CreateSqlCeDatabase(); + } + using (DisposableTimer.TraceDuration("InitializeDatabase")) + { + InitializeDatabase(); + } - CreateSqlCeDatabase(); - - InitializeDatabase(); - - //ensure the configuration matches the current version for tests - SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3); + //ensure the configuration matches the current version for tests + SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3); + } } protected override void SetupApplicationContext() diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 490ab33751..d03511cc00 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -28,17 +28,23 @@ namespace Umbraco.Tests.TestHelpers { base.Initialize(); - TestHelper.InitializeContentDirectories(); - TestHelper.EnsureUmbracoSettingsConfig(); + using (DisposableTimer.TraceDuration("init")) + { + TestHelper.InitializeContentDirectories(); + //TestHelper.EnsureUmbracoSettingsConfig(); + + //Create the legacy prop-eds mapping + LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); + + SetupPluginManager(); + + SetupApplicationContext(); + + InitializeMappers(); + + FreezeResolution(); + } - //Create the legacy prop-eds mapping - LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); - - SetupPluginManager(); - SetupApplicationContext(); - InitializeMappers(); - - FreezeResolution(); } [TearDown] diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs index b5524cad0f..64769511b0 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Tests.TestHelpers @@ -9,17 +10,25 @@ namespace Umbraco.Tests.TestHelpers [TestFixture] public abstract class BaseUmbracoConfigurationTest { + [TestFixtureSetUp] + public void InitializeFixture() + { + TestHelper.SetupLog4NetForTests(); + } + [SetUp] public virtual void Initialize() { - TestHelper.SetupLog4NetForTests(); + using (DisposableTimer.TraceDuration < BaseUmbracoConfigurationTest>("init")) + { + //mock the Umbraco settings that we need + var settings = SettingsForTests.GetMockSettings(); + //sets the global singleton to use the mocked format + SettingsForTests.ConfigureSettings(settings); + //set our local variable for tests to use (preferably) + UmbracoSettings = settings; + } - //mock the Umbraco settings that we need - var settings = SettingsForTests.GetMockSettings(); - //sets the global singleton to use the mocked format - SettingsForTests.ConfigureSettings(settings); - //set our local variable for tests to use (preferably) - UmbracoSettings = settings; } [TearDown] diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index ae28f50b6b..e9bee8a2a1 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -113,27 +113,27 @@ namespace Umbraco.Tests.TestHelpers } } - //TODO: With the new config updates, I'm pretty sure this isn't needed? - public static void EnsureUmbracoSettingsConfig() - { - var currDir = new DirectoryInfo(CurrentAssemblyDirectory); + ////TODO: With the new config updates, I'm pretty sure this isn't needed? + //public static void EnsureUmbracoSettingsConfig() + //{ + // var currDir = new DirectoryInfo(CurrentAssemblyDirectory); - var configPath = Path.Combine(currDir.Parent.Parent.FullName, "config"); - if (Directory.Exists(configPath) == false) - Directory.CreateDirectory(configPath); + // var configPath = Path.Combine(currDir.Parent.Parent.FullName, "config"); + // if (Directory.Exists(configPath) == false) + // Directory.CreateDirectory(configPath); - var umbracoSettingsFile = Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"); - if (File.Exists(umbracoSettingsFile) == false) - File.Copy( - currDir.Parent.Parent.Parent.GetDirectories("Umbraco.Web.UI") - .First() - .GetDirectories("config").First() - .GetFiles("umbracoSettings.Release.config").First().FullName, - Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"), - true); + // var umbracoSettingsFile = Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"); + // if (File.Exists(umbracoSettingsFile) == false) + // File.Copy( + // currDir.Parent.Parent.Parent.GetDirectories("Umbraco.Web.UI") + // .First() + // .GetDirectories("config").First() + // .GetFiles("umbracoSettings.Release.config").First().FullName, + // Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"), + // true); - //Core.Configuration.LegacyUmbracoSettings.SettingsFilePath = IOHelper.MapPath(SystemDirectories.Config + Path.DirectorySeparatorChar, false); - } + // //Core.Configuration.LegacyUmbracoSettings.SettingsFilePath = IOHelper.MapPath(SystemDirectories.Config + Path.DirectorySeparatorChar, false); + //} public static void CleanUmbracoSettingsConfig() { diff --git a/src/Umbraco.Tests/unit-test-log4net.config b/src/Umbraco.Tests/unit-test-log4net.config index 0922e4d932..1b4fc414e2 100644 --- a/src/Umbraco.Tests/unit-test-log4net.config +++ b/src/Umbraco.Tests/unit-test-log4net.config @@ -14,7 +14,14 @@ - + + + + + + + + From 0878de4a8b060a1c980af08175536b9749407b44 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 16:19:46 +1100 Subject: [PATCH 14/22] Fixes tree service from showing that there is children when there isn't, fixes container node issues: U4-3540 When creating a Container type node - the list view shows all children of the item it's being created underneath U4-3541 media tree is not catering for container types Adds container style --- .../src/common/services/tree.service.js | 9 +++--- .../listview/listview.controller.js | 10 ++++++- .../Models/Trees/TreeNodeExtensions.cs | 12 ++++++++ .../Trees/ContentTreeController.cs | 28 +++++++++++++------ .../Trees/ContentTreeControllerBase.cs | 8 ++++++ src/Umbraco.Web/Trees/MediaTreeController.cs | 18 +++++++++++- 6 files changed, 70 insertions(+), 15 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 f8266168b1..ba62feff60 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 @@ -227,12 +227,13 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc return this.getChildren(args) .then(function(data) { - //set state to done and expand + //set state to done and expand (only if there actually are children!) args.node.loading = false; args.node.children = data; - args.node.expanded = true; - args.node.hasChildren = true; - + if (args.node.children && args.node.children.length > 0) { + args.node.expanded = true; + args.node.hasChildren = true; + } return data; }, function(reason) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index bb70bd2a15..401d47f03d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -74,7 +74,9 @@ angular.module("umbraco") $scope.selectAll = function ($event) { var checkbox = $event.target; - + if (!angular.isArray($scope.listViewResultSet.items)) { + return; + } for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { var entity = $scope.listViewResultSet.items[i]; entity.selected = checkbox.checked; @@ -82,12 +84,18 @@ angular.module("umbraco") }; $scope.isSelectedAll = function () { + if (!angular.isArray($scope.listViewResultSet.items)) { + return false; + } return _.every($scope.listViewResultSet.items, function (item) { return item.selected; }); }; $scope.isAnythingSelected = function () { + if (!angular.isArray($scope.listViewResultSet.items)) { + return false; + } return _.some($scope.listViewResultSet.items, function (item) { return item.selected; }); diff --git a/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs b/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs index 13f36aa2f8..8e97d527b4 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs @@ -14,6 +14,18 @@ treeNode.AdditionalData[LegacyJsCallbackKey] = jsCallback; } + /// + /// Sets the node style to show that it is currently protected publicly + /// + /// + public static void SetContainerStyle(this TreeNode treeNode) + { + if (treeNode.CssClasses.Contains("is-container") == false) + { + treeNode.CssClasses.Add("is-container"); + } + } + /// /// Sets the node style to show that it is currently protected publicly /// diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 9be4a93311..2203a8ef89 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -52,12 +52,21 @@ namespace Umbraco.Web.Trees get { return Security.CurrentUser.StartContentId; } } + /// + /// Gets the tree nodes for the given id + /// + /// + /// + /// + /// + /// If the content item is a container node then we will not return anything + /// protected override TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings) { + var nodes = new TreeNodeCollection(); + var entities = GetChildEntities(id); - var nodes = new TreeNodeCollection(); - foreach (var entity in entities) { var e = (UmbracoEntity)entity; @@ -65,24 +74,25 @@ namespace Umbraco.Web.Trees var allowedUserOptions = GetAllowedUserMenuItemsForNode(e); if (CanUserAccessNode(e, allowedUserOptions)) { - var hasChildren = e.HasChildren; //Special check to see if it ia a container, if so then we'll hide children. - if (entity.AdditionalData.ContainsKey("IsContainer") && entity.AdditionalData["IsContainer"] is bool && (bool) entity.AdditionalData["IsContainer"]) - { - hasChildren = false; - } - + var isContainer = entity.AdditionalData.ContainsKey("IsContainer") + && entity.AdditionalData["IsContainer"] is bool + && (bool) entity.AdditionalData["IsContainer"]; + var node = CreateTreeNode( e.Id.ToInvariantString(), id, queryStrings, e.Name, e.ContentTypeIcon, - hasChildren); + e.HasChildren && (isContainer == false)); node.AdditionalData.Add("contentType", e.ContentTypeAlias); + if (isContainer) + node.SetContainerStyle(); + if (e.IsPublished == false) node.SetNotPublishedStyle(); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 5b9a6181f3..2261d038ec 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -83,6 +83,14 @@ namespace Umbraco.Web.Trees return nodes; } + //before we get the children we need to see if this is a container node + var current = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.Document); + if (current != null && current.AdditionalData.ContainsKey("IsContainer") && current.AdditionalData["IsContainer"] is bool && (bool)current.AdditionalData["IsContainer"]) + { + //no children! + return new TreeNodeCollection(); + } + return PerformGetTreeNodes(id, queryStrings); } diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index fa247eb87d..b091fbd89c 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -55,9 +55,25 @@ namespace Umbraco.Web.Trees foreach (var entity in entities) { var e = (UmbracoEntity)entity; - var node = CreateTreeNode(e.Id.ToInvariantString(), id, queryStrings, e.Name, e.ContentTypeIcon, e.HasChildren); + + //Special check to see if it ia a container, if so then we'll hide children. + var isContainer = entity.AdditionalData.ContainsKey("IsContainer") + && entity.AdditionalData["IsContainer"] is bool + && (bool)entity.AdditionalData["IsContainer"]; + var node = CreateTreeNode( + e.Id.ToInvariantString(), + id, + queryStrings, + e.Name, + e.ContentTypeIcon, + e.HasChildren && (isContainer == false)); + node.AdditionalData.Add("contentType", e.ContentTypeAlias); + + if (isContainer) + node.SetContainerStyle(); + nodes.Add(node); } return nodes; From 4545af0a2178264c2ccc5ca048b86a97dd7a9a12 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 16:56:51 +1100 Subject: [PATCH 15/22] Boom! container nodes work for both media and content, fixes: U4-3540 When creating a Container type node - the list view shows all children of the item it's being created underneath U4-3541 media tree is not catering for container types --- .../listview/listview.controller.js | 425 +++++++++--------- .../propertyeditors/listview/listview.html | 201 +++++---- src/Umbraco.Web/Editors/ContentController.cs | 12 +- src/Umbraco.Web/Editors/MediaController.cs | 7 +- .../Models/Mapping/ContentModelMapper.cs | 2 +- .../Models/Mapping/MediaModelMapper.cs | 7 +- .../Mapping/TabsAndPropertiesResolver.cs | 16 +- .../Trees/ContentTreeControllerBase.cs | 2 +- 8 files changed, 364 insertions(+), 308 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 401d47f03d..4b8b0b99ef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -1,221 +1,244 @@ -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.ListViewController", - function ($rootScope, $scope, $routeParams, contentResource, contentTypeResource, notificationsService, iconHelper, dialogService) { +function listViewController($rootScope, $scope, $routeParams, $injector, notificationsService, iconHelper, dialogService) { - $scope.actionInProgress = false; - $scope.listViewResultSet = { - totalPages: 0, - items: [] - }; + //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content + // that isn't created yet, if we continue this will use the parent id in the route params which isn't what + // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove + // the list view tab entirely when it's new. + if ($routeParams.create) { + $scope.isNew = true; + return; + } - $scope.options = { - pageSize: 10, - pageNumber: 1, - filter: '', - orderBy: 'Id', - orderDirection: "desc" - }; + //Now we need to check if this is for media or content because that will depend on the resources we use + var contentResource, contentTypeResource; + if ($scope.model.config.entityType && $scope.model.config.entityType === "media") { + contentResource = $injector.get('mediaResource'); + contentTypeResource = $injector.get('mediaTypeResource'); + $scope.entityType = "media"; + } + else { + contentResource = $injector.get('contentResource'); + contentTypeResource = $injector.get('contentTypeResource'); + $scope.entityType = "content"; + } + + $scope.isNew = false; + $scope.actionInProgress = false; + $scope.listViewResultSet = { + totalPages: 0, + items: [] + }; + + $scope.options = { + pageSize: 10, + pageNumber: 1, + filter: '', + orderBy: 'Id', + orderDirection: "desc" + }; - $scope.next = function () { - if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) { - $scope.options.pageNumber++; - $scope.reloadView($scope.contentId); - } - }; + $scope.next = function () { + if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber++; + $scope.reloadView($scope.contentId); + } + }; - $scope.goToPage = function (pageNumber) { - $scope.options.pageNumber = pageNumber + 1; - $scope.reloadView($scope.contentId); - }; + $scope.goToPage = function (pageNumber) { + $scope.options.pageNumber = pageNumber + 1; + $scope.reloadView($scope.contentId); + }; - $scope.sort = function (field) { + $scope.sort = function (field) { - $scope.options.orderBy = field; + $scope.options.orderBy = field; - if ($scope.options.orderDirection === "desc") { - $scope.options.orderDirection = "asc"; - } else { - $scope.options.orderDirection = "desc"; - } + if ($scope.options.orderDirection === "desc") { + $scope.options.orderDirection = "asc"; + } else { + $scope.options.orderDirection = "desc"; + } - $scope.reloadView($scope.contentId); - }; + $scope.reloadView($scope.contentId); + }; - $scope.prev = function () { - if ($scope.options.pageNumber > 1) { - $scope.options.pageNumber--; - $scope.reloadView($scope.contentId); - } - }; + $scope.prev = function () { + if ($scope.options.pageNumber > 1) { + $scope.options.pageNumber--; + $scope.reloadView($scope.contentId); + } + }; - /*Loads the search results, based on parameters set in prev,next,sort and so on*/ - /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state - with simple values */ + /*Loads the search results, based on parameters set in prev,next,sort and so on*/ + /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state + with simple values */ - $scope.reloadView = function (id) { - contentResource.getChildren(id, $scope.options).then(function (data) { + $scope.reloadView = function (id) { + contentResource.getChildren(id, $scope.options).then(function (data) { - $scope.listViewResultSet = data; - $scope.pagination = []; + $scope.listViewResultSet = data; + $scope.pagination = []; - for (var i = $scope.listViewResultSet.totalPages - 1; i >= 0; i--) { - $scope.pagination[i] = { index: i, name: i + 1 }; - } - - if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { - $scope.options.pageNumber = $scope.listViewResultSet.totalPages; - } - - }); - }; - - $scope.selectAll = function ($event) { - var checkbox = $event.target; - if (!angular.isArray($scope.listViewResultSet.items)) { - return; - } - for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { - var entity = $scope.listViewResultSet.items[i]; - entity.selected = checkbox.checked; - } - }; - - $scope.isSelectedAll = function () { - if (!angular.isArray($scope.listViewResultSet.items)) { - return false; - } - return _.every($scope.listViewResultSet.items, function (item) { - return item.selected; - }); - }; - - $scope.isAnythingSelected = function () { - if (!angular.isArray($scope.listViewResultSet.items)) { - return false; - } - return _.some($scope.listViewResultSet.items, function (item) { - return item.selected; - }); - }; - - $scope.getIcon = function (entry) { - return iconHelper.convertFromLegacyIcon(entry.icon); - }; - - $scope.delete = function () { - var selected = _.filter($scope.listViewResultSet.items, function (item) { - return item.selected; - }); - var total = selected.length; - if (total === 0) { - return; - } - - if (confirm("Sure you want to delete?") == true) { - $scope.actionInProgress = true; - $scope.bulkStatus = "Starting with delete"; - var current = 1; - - for (var i = 0; i < selected.length; i++) { - $scope.bulkStatus = "Deleted doc " + current + " out of " + total + " documents"; - contentResource.deleteById(selected[i].id).then(function (data) { - if (current === total) { - notificationsService.success("Bulk action", "Deleted " + total + "documents"); - $scope.bulkStatus = ""; - $scope.reloadView($scope.contentId); - $scope.actionInProgress = false; - } - current++; - }); - } - } - - }; - - $scope.publish = function () { - var selected = _.filter($scope.listViewResultSet.items, function (item) { - return item.selected; - }); - var total = selected.length; - if (total === 0) { - return; - } - - $scope.actionInProgress = true; - $scope.bulkStatus = "Starting with publish"; - var current = 1; - - for (var i = 0; i < selected.length; i++) { - $scope.bulkStatus = "Publishing " + current + " out of " + total + " documents"; - - contentResource.publishById(selected[i].id) - .then(function (content) { - if (current == total) { - notificationsService.success("Bulk action", "Published " + total + "documents"); - $scope.bulkStatus = ""; - $scope.reloadView($scope.contentId); - $scope.actionInProgress = false; - } - current++; - }, function (err) { - - $scope.bulkStatus = ""; - $scope.reloadView($scope.contentId); - $scope.actionInProgress = false; - - //if there are validation errors for publishing then we need to show them - if (err.status === 400 && err.data && err.data.Message) { - notificationsService.error("Publish error", err.data.Message); - } - else { - dialogService.ysodDialog(err); - } - }); - - } - }; - - $scope.unpublish = function () { - var selected = _.filter($scope.listViewResultSet.items, function (item) { - return item.selected; - }); - var total = selected.length; - if (total === 0) { - return; - } - - $scope.actionInProgress = true; - $scope.bulkStatus = "Starting with publish"; - var current = 1; - - for (var i = 0; i < selected.length; i++) { - $scope.bulkStatus = "Unpublishing " + current + " out of " + total + " documents"; - - contentResource.unPublish(selected[i].id) - .then(function (content) { - - if (current == total) { - notificationsService.success("Bulk action", "Published " + total + "documents"); - $scope.bulkStatus = ""; - $scope.reloadView($scope.contentId); - $scope.actionInProgress = false; - } - - current++; - }); - } - }; - - if ($routeParams.id) { - $scope.pagination = new Array(10); - $scope.listViewAllowedTypes = contentTypeResource.getAllowedTypes($routeParams.id); - $scope.reloadView($routeParams.id); - - $scope.contentId = $routeParams.id; + for (var i = $scope.listViewResultSet.totalPages - 1; i >= 0; i--) { + $scope.pagination[i] = { index: i, name: i + 1 }; + } + if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber = $scope.listViewResultSet.totalPages; } }); + }; + + $scope.selectAll = function ($event) { + var checkbox = $event.target; + if (!angular.isArray($scope.listViewResultSet.items)) { + return; + } + for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { + var entity = $scope.listViewResultSet.items[i]; + entity.selected = checkbox.checked; + } + }; + + $scope.isSelectedAll = function () { + if (!angular.isArray($scope.listViewResultSet.items)) { + return false; + } + return _.every($scope.listViewResultSet.items, function (item) { + return item.selected; + }); + }; + + $scope.isAnythingSelected = function () { + if (!angular.isArray($scope.listViewResultSet.items)) { + return false; + } + return _.some($scope.listViewResultSet.items, function (item) { + return item.selected; + }); + }; + + $scope.getIcon = function (entry) { + return iconHelper.convertFromLegacyIcon(entry.icon); + }; + + $scope.delete = function () { + var selected = _.filter($scope.listViewResultSet.items, function (item) { + return item.selected; + }); + var total = selected.length; + if (total === 0) { + return; + } + + if (confirm("Sure you want to delete?") == true) { + $scope.actionInProgress = true; + $scope.bulkStatus = "Starting with delete"; + var current = 1; + + for (var i = 0; i < selected.length; i++) { + $scope.bulkStatus = "Deleted doc " + current + " out of " + total + " documents"; + contentResource.deleteById(selected[i].id).then(function (data) { + if (current === total) { + notificationsService.success("Bulk action", "Deleted " + total + "documents"); + $scope.bulkStatus = ""; + $scope.reloadView($scope.contentId); + $scope.actionInProgress = false; + } + current++; + }); + } + } + + }; + + $scope.publish = function () { + var selected = _.filter($scope.listViewResultSet.items, function (item) { + return item.selected; + }); + var total = selected.length; + if (total === 0) { + return; + } + + $scope.actionInProgress = true; + $scope.bulkStatus = "Starting with publish"; + var current = 1; + + for (var i = 0; i < selected.length; i++) { + $scope.bulkStatus = "Publishing " + current + " out of " + total + " documents"; + + contentResource.publishById(selected[i].id) + .then(function (content) { + if (current == total) { + notificationsService.success("Bulk action", "Published " + total + "documents"); + $scope.bulkStatus = ""; + $scope.reloadView($scope.contentId); + $scope.actionInProgress = false; + } + current++; + }, function (err) { + + $scope.bulkStatus = ""; + $scope.reloadView($scope.contentId); + $scope.actionInProgress = false; + + //if there are validation errors for publishing then we need to show them + if (err.status === 400 && err.data && err.data.Message) { + notificationsService.error("Publish error", err.data.Message); + } + else { + dialogService.ysodDialog(err); + } + }); + + } + }; + + $scope.unpublish = function () { + var selected = _.filter($scope.listViewResultSet.items, function (item) { + return item.selected; + }); + var total = selected.length; + if (total === 0) { + return; + } + + $scope.actionInProgress = true; + $scope.bulkStatus = "Starting with publish"; + var current = 1; + + for (var i = 0; i < selected.length; i++) { + $scope.bulkStatus = "Unpublishing " + current + " out of " + total + " documents"; + + contentResource.unPublish(selected[i].id) + .then(function (content) { + + if (current == total) { + notificationsService.success("Bulk action", "Published " + total + "documents"); + $scope.bulkStatus = ""; + $scope.reloadView($scope.contentId); + $scope.actionInProgress = false; + } + + current++; + }); + } + }; + + if ($routeParams.id) { + $scope.pagination = new Array(10); + $scope.listViewAllowedTypes = contentTypeResource.getAllowedTypes($routeParams.id); + $scope.reloadView($routeParams.id); + + $scope.contentId = $routeParams.id; + + } + +} + +angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 23feb61dc3..4cb351f149 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -1,103 +1,114 @@ - -
-
- +
+ +
- - - - - - - - - - + - - +
+
-
- - - - - - +
- Name - - Last edited - - Updated by -
- - -
- + + + + + + + - - {{result.name}}{{result.updateDate|date:'medium'}} - - {{result.owner.name}}
+ + + + + + + + + - - - + -
  • - {{$index + 1}} -
  • + + + + + + + - - - -
    + + Name + + Last edited + + Updated by + +
    + + +
    +
    -
    - -
    -
    + -
  • - Next -
  • - - +
    + {{result.name}}{{result.updateDate|date:'medium'}} + + {{result.owner.name}} + +
    -
    \ No newline at end of file + + + +
    +
    + + + + + + +
    +
    \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index c7f04e6ac4..65cc5cf6b6 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -95,7 +95,10 @@ namespace Umbraco.Web.Editors ///
    /// /// - /// + /// + /// If this is a container type, we'll remove the umbContainerView tab for a new item since + /// it cannot actually list children if it doesn't exist yet. + /// public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) { var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); @@ -105,7 +108,12 @@ namespace Umbraco.Web.Editors } var emptyContent = new Content("", parentId, contentType); - return Mapper.Map(emptyContent); + var mapped = Mapper.Map(emptyContent); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == "umbContainerView"); + mapped.Tabs = mapped.Tabs.Except(new[] {containerTab}); + return mapped; } /// diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 844d8d5fc8..321704e828 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -152,7 +152,12 @@ namespace Umbraco.Web.Editors } var emptyContent = new Core.Models.Media("", parentId, contentType); - return Mapper.Map(emptyContent); + var mapped = Mapper.Map(emptyContent); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == "umbContainerView"); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; } /// diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index a0398328ca..d1bd00d2f3 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -100,7 +100,7 @@ namespace Umbraco.Web.Models.Mapping if (content.ContentType.IsContainer) { - TabsAndPropertiesResolver.AddContainerView(display); + TabsAndPropertiesResolver.AddContainerView(display, "content"); } TabsAndPropertiesResolver.MapGenericProperties( diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 75e3d0098e..a105048f65 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -61,12 +61,11 @@ namespace Umbraco.Web.Models.Mapping private static void MapGenericCustomProperties(IMedia media, MediaItemDisplay display) { - /* - * Should this be added? if so we need some changes in the UI tho. + if (media.ContentType.IsContainer) { - TabsAndPropertiesResolver.AddContainerView(display); - }*/ + TabsAndPropertiesResolver.AddContainerView(display, "media"); + } TabsAndPropertiesResolver.MapGenericProperties(media, display); } diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 62485daae6..b4fcb412c4 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -108,11 +108,17 @@ namespace Umbraco.Web.Models.Mapping } - internal static void AddContainerView(TabbedContentItem display) + /// + /// Adds the container (listview) tab to the document + /// + /// + /// + /// This must be either 'content' or 'media' + internal static void AddContainerView(TabbedContentItem display, string entityType) where TPersisted : IContentBase { var listViewTab = new Tab(); - listViewTab.Alias = "containerView"; + listViewTab.Alias = "umbContainerView"; listViewTab.Label = ui.Text("content", "childItems"); listViewTab.Id = 25; listViewTab.IsActive = true; @@ -124,7 +130,11 @@ namespace Umbraco.Web.Models.Mapping Label = "", Value = null, View = "listview", - HideLabel = true + HideLabel = true, + Config = new Dictionary + { + {"entityType", entityType} + } }); listViewTab.Properties = listViewProperties; diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 2261d038ec..82d9fcb9b2 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -84,7 +84,7 @@ namespace Umbraco.Web.Trees } //before we get the children we need to see if this is a container node - var current = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.Document); + var current = Services.EntityService.Get(int.Parse(id), UmbracoObjectType); if (current != null && current.AdditionalData.ContainsKey("IsContainer") && current.AdditionalData["IsContainer"] is bool && (bool)current.AdditionalData["IsContainer"]) { //no children! From d446d84cf596bc7b7347775f5a6e4e1658f771ef Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 17:03:29 +1100 Subject: [PATCH 16/22] fixes failing unit test due to default package url change --- .../UmbracoSettings/PackageRepositoriesElementTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/PackageRepositoriesElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/PackageRepositoriesElementTests.cs index cb82978c72..fb2dcff4a6 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/PackageRepositoriesElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/PackageRepositoriesElementTests.cs @@ -15,13 +15,13 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(0).Name == "Umbraco package Repository"); Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(0).HasCustomWebServiceUrl == false); Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(0).WebServiceUrl == "/umbraco/webservices/api/repository.asmx"); - Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(0).RepositoryUrl == "http://packages.umbraco.org"); + Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(0).RepositoryUrl == "http://our.umbraco.org"); Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(1).Id == Guid.Parse("163245E0-CD22-44B6-841A-1B9B9D2E955F")); Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(1).Name == "Test Repo"); Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(1).HasCustomWebServiceUrl == false); Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(0).WebServiceUrl == "/umbraco/webservices/api/repository.asmx"); - Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(0).RepositoryUrl == "http://packages.umbraco.org"); + Assert.IsTrue(SettingsSection.PackageRepositories.Repositories.ElementAt(0).RepositoryUrl == "http://our.umbraco.org"); } } } \ No newline at end of file From 2bd91d619d6cefbf473d9caea95d1481e724a39b Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 17:04:45 +1100 Subject: [PATCH 17/22] fixes failing unit test due to default value change for keep user logged in --- .../Configurations/UmbracoSettings/SecurityElementTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs index 219721c767..222f598011 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs @@ -8,7 +8,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public void KeepUserLoggedIn() { - Assert.IsTrue(SettingsSection.Security.KeepUserLoggedIn == true); + Assert.IsTrue(SettingsSection.Security.KeepUserLoggedIn == false); } [Test] public void HideDisabledUsersInBackoffice() From 94cc49e67ee8eb5f3c8790650e18643c598cfe8b Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 17:15:59 +1100 Subject: [PATCH 18/22] Fixed the model mapping tests --- .../UmbracoSettings/SecurityElement.cs | 2 +- .../UmbracoSettings/SecurityElementTests.cs | 2 +- .../Models/Mapping/ContentWebModelMappingTests.cs | 12 ++++++------ .../TestHelpers/BaseDatabaseFactoryTest.cs | 15 +++------------ 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs index d3160a216b..33a8a8584b 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 - false); + true); } } diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs index 222f598011..219721c767 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs @@ -8,7 +8,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public void KeepUserLoggedIn() { - Assert.IsTrue(SettingsSection.Security.KeepUserLoggedIn == false); + Assert.IsTrue(SettingsSection.Security.KeepUserLoggedIn == true); } [Test] public void HideDisabledUsersInBackoffice() diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index 02849050bb..4d62dcf809 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -30,13 +30,13 @@ namespace Umbraco.Tests.Models.Mapping get { return DatabaseBehavior.NewSchemaPerFixture; } } - protected override void FreezeResolution() - { - //PropertyEditorResolver.Current = new PropertyEditorResolver( - // () => new List {typeof (TestPropertyEditor)}); + //protected override void FreezeResolution() + //{ + // PropertyEditorResolver.Current = new PropertyEditorResolver( + // () => PluginManager.Current.ResolvePropertyEditors()); - base.FreezeResolution(); - } + // base.FreezeResolution(); + //} [Test] public void To_Media_Item_Simple() diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 7c918008c3..46caba40c5 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -80,18 +80,9 @@ namespace Umbraco.Tests.TestHelpers using (DisposableTimer.TraceDuration("init")) { - using (DisposableTimer.TraceDuration("DatabaseContext.Initialize")) - { - DatabaseContext.Initialize(dbFactory.ProviderName, dbFactory.ConnectionString); - } - using (DisposableTimer.TraceDuration("CreateSqlCeDatabase")) - { - CreateSqlCeDatabase(); - } - using (DisposableTimer.TraceDuration("InitializeDatabase")) - { - InitializeDatabase(); - } + DatabaseContext.Initialize(dbFactory.ProviderName, dbFactory.ConnectionString); + CreateSqlCeDatabase(); + InitializeDatabase(); //ensure the configuration matches the current version for tests SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3); From 2784e2a22da1825bc55a94f0fa17ee2892f5807c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 17:30:26 +1100 Subject: [PATCH 19/22] removes ref to missing file --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index c80b2e6147..39d408c1d6 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -618,13 +618,6 @@ treeInit.aspx - - MyTest.ascx - ASPXCodeBehind - - - MyTest.ascx - @@ -2195,7 +2188,6 @@ - Designer @@ -2632,6 +2624,7 @@ + From 259d8e03db888b0445b5e256058bd0bdcaaafde3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 17:54:01 +1100 Subject: [PATCH 20/22] completes: U4-3300 Need to test content service copy API with tags - since there's 'special' code in there to handle it that's not been tested with the new changes - with test --- src/Umbraco.Core/Services/ContentService.cs | 11 ++++------ .../Services/ContentServiceTests.cs | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 93bd9a6408..2dd8287a7c 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1170,14 +1170,11 @@ namespace Umbraco.Core.Services } } - //Special case for the Tags DataType - if (content.Properties.Any(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.TagsAlias)) + //Special case for the associated tags + var tags = uow.Database.Fetch("WHERE nodeId = @Id", new { Id = content.Id }); + foreach (var tag in tags) { - var tags = uow.Database.Fetch("WHERE nodeId = @Id", new { Id = content.Id }); - foreach (var tag in tags) - { - uow.Database.Insert(new TagRelationshipDto { NodeId = copy.Id, TagId = tag.TagId }); - } + uow.Database.Insert(new TagRelationshipDto { NodeId = copy.Id, TagId = tag.TagId, PropertyTypeId = tag.PropertyTypeId }); } } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index cf1847ac60..fd5349a0a9 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -865,6 +865,27 @@ namespace Umbraco.Tests.Services //Assert.AreNotEqual(content.Name, copy.Name); } + [Test] + public void Can_Copy_Content_With_Tags() + { + // Arrange + var contentService = ServiceContext.ContentService; + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + var temp = MockedContent.CreateSimpleContent(contentType, "Simple Text Page", -1); + var prop = temp.Properties.First(); + temp.SetTags(prop.Alias, new[] {"hello", "world"}, true); + var status = contentService.PublishWithStatus(temp); + + // Act + var copy = contentService.Copy(temp, temp.ParentId, false, 0); + + // Assert + var copiedTags = ServiceContext.TagService.GetTagsForEntity(copy.Id).ToArray(); + Assert.AreEqual(2, copiedTags.Count()); + Assert.AreEqual("hello", copiedTags[0].Text); + Assert.AreEqual("world", copiedTags[1].Text); + } + [Test, NUnit.Framework.Ignore] public void Can_Send_To_Publication() { } From 830d6d914fe66fb6e14463314f4cb2ad345e2552 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 18:21:55 +1100 Subject: [PATCH 21/22] puts the test init code back in that shouldn't need to be there but the server is failing now for some reason. --- .../TestHelpers/BaseUmbracoApplicationTest.cs | 2 +- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 36 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index d03511cc00..48b7c0b17f 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -31,7 +31,7 @@ namespace Umbraco.Tests.TestHelpers using (DisposableTimer.TraceDuration("init")) { TestHelper.InitializeContentDirectories(); - //TestHelper.EnsureUmbracoSettingsConfig(); + TestHelper.EnsureUmbracoSettingsConfig(); //Create the legacy prop-eds mapping LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index e9bee8a2a1..ae28f50b6b 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -113,27 +113,27 @@ namespace Umbraco.Tests.TestHelpers } } - ////TODO: With the new config updates, I'm pretty sure this isn't needed? - //public static void EnsureUmbracoSettingsConfig() - //{ - // var currDir = new DirectoryInfo(CurrentAssemblyDirectory); + //TODO: With the new config updates, I'm pretty sure this isn't needed? + public static void EnsureUmbracoSettingsConfig() + { + var currDir = new DirectoryInfo(CurrentAssemblyDirectory); - // var configPath = Path.Combine(currDir.Parent.Parent.FullName, "config"); - // if (Directory.Exists(configPath) == false) - // Directory.CreateDirectory(configPath); + var configPath = Path.Combine(currDir.Parent.Parent.FullName, "config"); + if (Directory.Exists(configPath) == false) + Directory.CreateDirectory(configPath); - // var umbracoSettingsFile = Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"); - // if (File.Exists(umbracoSettingsFile) == false) - // File.Copy( - // currDir.Parent.Parent.Parent.GetDirectories("Umbraco.Web.UI") - // .First() - // .GetDirectories("config").First() - // .GetFiles("umbracoSettings.Release.config").First().FullName, - // Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"), - // true); + var umbracoSettingsFile = Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"); + if (File.Exists(umbracoSettingsFile) == false) + File.Copy( + currDir.Parent.Parent.Parent.GetDirectories("Umbraco.Web.UI") + .First() + .GetDirectories("config").First() + .GetFiles("umbracoSettings.Release.config").First().FullName, + Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"), + true); - // //Core.Configuration.LegacyUmbracoSettings.SettingsFilePath = IOHelper.MapPath(SystemDirectories.Config + Path.DirectorySeparatorChar, false); - //} + //Core.Configuration.LegacyUmbracoSettings.SettingsFilePath = IOHelper.MapPath(SystemDirectories.Config + Path.DirectorySeparatorChar, false); + } public static void CleanUmbracoSettingsConfig() { From febe81105bcff7305dfa6b38eabae5fecf9e4088 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2013 18:26:40 +1100 Subject: [PATCH 22/22] starts fixing custom membership provider problems with the member editor/tree - this now at least loads in the members section. --- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 8 ++++++-- src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index b16ee8bd00..5b18e5698b 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -75,8 +75,12 @@ namespace Umbraco.Web.Trees foreach (var apptree in appTrees) { //return the root nodes for each tree in the app - var rootNode = GetRootForMultipleAppTree(apptree, queryStrings); - collection.Add(rootNode); + var rootNode = GetRootForMultipleAppTree(apptree, queryStrings); + //This could be null if the tree decides not to return it's root (i.e. the member type tree does this when not in umbraco membership mode) + if (rootNode != null) + { + collection.Add(rootNode); + } } var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index f39c79fe8b..72c58f7e4d 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -82,6 +82,12 @@ namespace Umbraco.Web.Trees return Attempt.Fail(xmlTreeNodeAttempt.Exception); } + //the root can potentially be null, in that case we'll just return a null success which means it won't be included + if (xmlTreeNodeAttempt.Result == null) + { + return Attempt.Succeed(null); + } + var legacyController = new LegacyTreeController(xmlTreeNodeAttempt.Result, appTree.Alias, currentSection, urlHelper); var newRoot = legacyController.GetRootNode(formCollection);