diff --git a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs new file mode 100644 index 0000000000..a02b9003d6 --- /dev/null +++ b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.ModelBinding; + +namespace Umbraco.Core.Models.Validation +{ + /// + /// A custom validation attribute which adds additional metadata to the property to indicate that + /// the value is required to be persisted. + /// + /// + /// In Umbraco, we persist content even if it is invalid, however there are some properties that are absolutely required + /// in order to be persisted such as the Name of the content item. This attribute is re-usable to check for these types of + /// properties over any sort of model. + /// + public class RequiredForPersistenceAttribute : RequiredAttribute + { + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c060e218b2..3e4db2e6ae 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -251,6 +251,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js index 9c170ee9dc..7a55a3b419 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js @@ -4,7 +4,7 @@ * @restrict E **/ angular.module("umbraco.directives") - .directive('umbTree', function ($compile, $log, $q, treeService, notificationsService, $timeout) { + .directive('umbTree', function ($compile, $log, $q, $rootScope, treeService, notificationsService, $timeout) { return { restrict: 'E', @@ -15,8 +15,7 @@ angular.module("umbraco.directives") section: '@', showoptions: '@', showheader: '@', - cachekey: '@', - callback: '=' + cachekey: '@' }, compile: function (element, attrs) { @@ -34,7 +33,7 @@ angular.module("umbraco.directives") ''; } template += '
    ' + - '' + + '' + '
' + '' + ''; @@ -56,9 +55,7 @@ angular.module("umbraco.directives") /** Helper function to emit tree events */ function emitEvent(eventName, args) { - if (scope.callback) { - $(scope.callback).trigger(eventName, args); - } + $rootScope.$broadcast(eventName, args); } /** Method to load in the tree data */ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js index eee71fa822..61ae1719c7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js @@ -18,7 +18,7 @@ */ angular.module("umbraco.directives") -.directive('umbTreeItem', function($compile, $http, $templateCache, $interpolate, $log, $location, treeService, notificationsService) { +.directive('umbTreeItem', function ($compile, $http, $templateCache, $interpolate, $log, $location, $rootScope, treeService, notificationsService) { return { restrict: 'E', replace: true, @@ -46,11 +46,9 @@ angular.module("umbraco.directives") var enableDeleteAnimations = true; /** Helper function to emit tree events */ - function emitEvent(eventName, args){ - if(scope.callback){ - $(scope.callback).trigger(eventName,args); - } + function emitEvent(eventName, args) { + $rootScope.$broadcast(eventName, args); } /** 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 0d8e1bc87c..abb9fa42ed 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 @@ -20,10 +20,7 @@ angular.module('umbraco.services') .factory('navigationService', function ($rootScope, $routeParams, $log, $location, $q, dialogService, treeService, notificationsService) { //TODO: would be nicer to set all of the options here first instead of implicitly below! - var ui = { - //tree event handler everyone can subscribe to (TODO: but why not use angular events instead of jquery ?) - tree: $({}) - }; + var ui = {}; function setMode(mode) { switch (mode) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.controller.js index 19582a99f5..522daaec8f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.controller.js @@ -3,7 +3,8 @@ angular.module("umbraco").controller("Umbraco.Dialogs.ContentPickerController", function ($scope) { $scope.treeCallback = $({}); - $scope.treeCallback.bind("treeNodeSelect", function(event, args){ + //$scope.treeCallback.bind("treeNodeSelect", function(event, args){ + $scope.$on("treeNodeSelect", function (ev, args) { args.event.preventDefault(); args.event.stopPropagation(); 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 a1801bd502..f4e6b62b25 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 @@ -33,20 +33,16 @@ function NavigationController($scope,$rootScope, $location, $log, navigationServ }); //this reacts to the options item in the tree - navigationService.ui.tree.bind("treeOptionsClick", function (ev, args) { - ev.stopPropagation(); - ev.preventDefault(); - + $scope.$on("treeOptionsClick", function (ev, args) { $scope.currentNode = args.node; args.scope = $scope; - navigationService.showMenu(ev, args); }); //this reacts to tree items themselves being clicked //the tree directive should not contain any handling, simply just bubble events - navigationService.ui.tree.bind("treeNodeSelect", function (ev, args) { - + $scope.$on("treeNodeSelect", function (ev, args) { + var n = args.node; //here we need to check for some legacy tree code diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html index 97905ebb35..bd28cc3dda 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html @@ -65,7 +65,7 @@
- +
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 429ba87931..6e440d873b 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -71,6 +71,7 @@ prompt AllRules.ruleset false + C:\Users\Shannon\AppData\Local\Temp\vs24C6.tmp\Debug\ bin\ @@ -96,6 +97,7 @@ prompt AllRules.ruleset false + C:\Users\Shannon\AppData\Local\Temp\vs24C6.tmp\Release\ diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 92da72dafe..da3c81b2b3 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -149,7 +149,7 @@ namespace Umbraco.Web.Editors // a message indicating this if (!ModelState.IsValid) { - if (ModelState["Name"] != null && ModelState["Name"].Errors.Any() + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && (contentItem.Action == ContentSaveAction.SaveNew || contentItem.Action == ContentSaveAction.PublishNew)) { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! @@ -157,6 +157,7 @@ namespace Umbraco.Web.Editors var forDisplay = _contentModelMapper.ToContentItemDisplay(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Forbidden, forDisplay)); + } //if the model state is not valid we cannot publish so change it to save diff --git a/src/Umbraco.Web/Editors/ValidationHelper.cs b/src/Umbraco.Web/Editors/ValidationHelper.cs new file mode 100644 index 0000000000..7e71512151 --- /dev/null +++ b/src/Umbraco.Web/Editors/ValidationHelper.cs @@ -0,0 +1,31 @@ +using System; +using System.ComponentModel; +using System.Linq; +using Umbraco.Core.Models.Validation; + +namespace Umbraco.Web.Editors +{ + internal class ValidationHelper + { + /// + /// This will check if any properties of the model are attributed with the RequiredForPersistenceAttribute attribute and if they are it will + /// check if that property validates, if it doesn't it means that the current model cannot be persisted because it doesn't have the necessary information + /// to be saved. + /// + /// + /// + /// + /// This is normally used for things like content creating when the name is empty since we cannot actually create a content item when the name is empty. + /// This is similar but different from the standard Required validator since we still allow content to be saved when validation fails but there are some + /// content fields that are absolutely mandatory for creating/saving. + /// + internal static bool ModelHasRequiredForPersistenceErrors(object model) + { + var requiredForPersistenceProperties = TypeDescriptor.GetProperties(model).Cast() + .Where(x => x.Attributes.Cast().Any(a => a is RequiredForPersistenceAttribute)); + + var validator = new RequiredForPersistenceAttribute(); + return requiredForPersistenceProperties.Any(p => !validator.IsValid(p.GetValue(model))); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index 270fecc33c..c1adf1126f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Newtonsoft.Json; using Umbraco.Core.Models; +using Umbraco.Core.Models.Validation; using Umbraco.Web.WebApi; namespace Umbraco.Web.Models.ContentEditing @@ -32,7 +33,7 @@ namespace Umbraco.Web.Models.ContentEditing public int Id { get; set; } [DataMember(Name = "name", IsRequired = true)] - [Required(AllowEmptyStrings = false, ErrorMessage = "Required")] + [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] public string Name { get; set; } [DataMember(Name = "properties")] diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 43d3725b83..5ce606f5e1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -303,6 +303,7 @@ + @@ -442,25 +443,16 @@ - - ASPXCodeBehind - - - ASPXCodeBehind - + + AssignDomain2.aspx - ASPXCodeBehind AssignDomain2.aspx - - ASPXCodeBehind - - - ASPXCodeBehind - + + @@ -485,9 +477,7 @@ - - ASPXCodeBehind - + @@ -560,138 +550,56 @@ - - ASPXCodeBehind - - - ASPXCodeBehind - + + - - ASPXCodeBehind - + - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - + + + + - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - + + + + + + + + + + + + + - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - + + + + + + + + - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - + + + + + + + + + - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - + + + + @@ -720,9 +628,7 @@ - - ASPXCodeBehind - + @@ -748,12 +654,8 @@ - - ASPXCodeBehind - - - ASPXCodeBehind - + + Code @@ -805,9 +707,7 @@ - - ASPXCodeBehind - + Code @@ -823,28 +723,24 @@ delete.aspx - ASPXCodeBehind delete.aspx editContent.aspx - ASPXCodeBehind editContent.aspx preview.aspx - ASPXCodeBehind preview.aspx publish.aspx - ASPXCodeBehind publish.aspx @@ -865,18 +761,15 @@ ImageViewer.ascx - ASPXCodeBehind ImageViewer.ascx ImageViewerUpdater.asmx - Component UploadMediaImage.ascx - ASPXCodeBehind UploadMediaImage.ascx @@ -884,7 +777,6 @@ GenericProperty.ascx - ASPXCodeBehind GenericProperty.ascx @@ -895,25 +787,19 @@ passwordChanger.ascx - ASPXCodeBehind passwordChanger.ascx - ASPXCodeBehind ProgressBar.ascx ProgressBar.ascx - - ASPXCodeBehind - - - Component - + + @@ -921,7 +807,6 @@ TreeControl.ascx - ASPXCodeBehind TreeControl.ascx @@ -934,7 +819,6 @@ DLRScripting.ascx - ASPXCodeBehind DLRScripting.ascx @@ -957,14 +841,12 @@ ChangePassword.ascx - ASPXCodeBehind ChangePassword.ascx DesktopMediaUploader.ascx - ASPXCodeBehind DesktopMediaUploader.ascx @@ -977,7 +859,6 @@ FeedProxy.aspx - ASPXCodeBehind FeedProxy.aspx @@ -1002,7 +883,6 @@ Applyskin.ascx - ASPXCodeBehind Applyskin.ascx @@ -1018,14 +898,12 @@ EditRelationType.aspx - ASPXCodeBehind EditRelationType.aspx NewRelationType.aspx - ASPXCodeBehind NewRelationType.aspx @@ -1033,20 +911,17 @@ RelationTypesWebService.asmx - Component Preview.aspx - ASPXCodeBehind Preview.aspx TemplateSkinning.aspx - ASPXCodeBehind TemplateSkinning.aspx @@ -1056,49 +931,42 @@ CssParser.aspx - ASPXCodeBehind CssParser.aspx ImageUploader.aspx - ASPXCodeBehind ImageUploader.aspx ModuleInjectionMacroRenderer.aspx - ASPXCodeBehind ModuleInjectionMacroRenderer.aspx ModuleInjector.aspx - ASPXCodeBehind ModuleInjector.aspx ModuleInstaller.aspx - ASPXCodeBehind ModuleInstaller.aspx ModuleSelector.ascx - ASPXCodeBehind ModuleSelector.ascx SkinCustomizer.ascx - ASPXCodeBehind SkinCustomizer.ascx @@ -1106,7 +974,6 @@ MemberSearch.ascx - ASPXCodeBehind MemberSearch.ascx @@ -1116,56 +983,48 @@ xsltVisualize.aspx - ASPXCodeBehind xsltVisualize.aspx insertMasterpageContent.aspx - ASPXCodeBehind insertMasterpageContent.aspx insertMasterpagePlaceholder.aspx - ASPXCodeBehind insertMasterpagePlaceholder.aspx mediaPicker.aspx - ASPXCodeBehind mediaPicker.aspx republish.aspx - ASPXCodeBehind republish.aspx search.aspx - ASPXCodeBehind search.aspx SendPublish.aspx - ASPXCodeBehind SendPublish.aspx content.ascx - ASPXCodeBehind content.ascx @@ -1175,42 +1034,36 @@ language.ascx - ASPXCodeBehind language.ascx media.ascx - ASPXCodeBehind media.ascx member.ascx - ASPXCodeBehind member.ascx nodeType.ascx - ASPXCodeBehind nodeType.ascx script.ascx - ASPXCodeBehind script.ascx simple.ascx - ASPXCodeBehind simple.ascx @@ -1220,222 +1073,189 @@ xslt.ascx - ASPXCodeBehind xslt.ascx LatestEdits.ascx - ASPXCodeBehind LatestEdits.ascx assemblyBrowser.aspx - ASPXCodeBehind assemblyBrowser.aspx autoDoc.aspx - ASPXCodeBehind autoDoc.aspx editDatatype.aspx - ASPXCodeBehind editDatatype.aspx BrowseRepository.aspx - ASPXCodeBehind BrowseRepository.aspx editPackage.aspx - ASPXCodeBehind editPackage.aspx installedPackage.aspx - ASPXCodeBehind installedPackage.aspx LoadNitros.ascx - ASPXCodeBehind LoadNitros.ascx SubmitPackage.aspx - ASPXCodeBehind SubmitPackage.aspx getXsltStatus.asmx - Component viewCacheItem.aspx - ASPXCodeBehind viewCacheItem.aspx xsltChooseExtension.aspx - ASPXCodeBehind xsltChooseExtension.aspx xsltInsertValueOf.aspx - ASPXCodeBehind xsltInsertValueOf.aspx about.aspx - ASPXCodeBehind about.aspx AssignDomain.aspx - ASPXCodeBehind AssignDomain.aspx create.aspx - ASPXCodeBehind create.aspx cruds.aspx - ASPXCodeBehind cruds.aspx emptyTrashcan.aspx - ASPXCodeBehind emptyTrashcan.aspx exportDocumenttype.aspx - ASPXCodeBehind imageViewer.aspx - ASPXCodeBehind imageViewer.aspx importDocumenttype.aspx - ASPXCodeBehind insertMacro.aspx - ASPXCodeBehind insertMacro.aspx insertTable.aspx - ASPXCodeBehind insertTable.aspx notifications.aspx - ASPXCodeBehind notifications.aspx installer.aspx - ASPXCodeBehind installer.aspx protectPage.aspx - ASPXCodeBehind protectPage.aspx RegexWs.aspx - ASPXCodeBehind RegexWs.aspx rollBack.aspx - ASPXCodeBehind rollBack.aspx sendToTranslation.aspx - ASPXCodeBehind sendToTranslation.aspx uploadImage.aspx - ASPXCodeBehind uploadImage.aspx viewAuditTrail.aspx - ASPXCodeBehind viewAuditTrail.aspx language.aspx - ASPXCodeBehind language.aspx @@ -1464,35 +1284,30 @@ EditMember.aspx - ASPXCodeBehind EditMember.aspx EditMemberGroup.aspx - ASPXCodeBehind EditMemberGroup.aspx EditMemberType.aspx - ASPXCodeBehind EditMemberType.aspx search.aspx - ASPXCodeBehind search.aspx ViewMembers.aspx - ASPXCodeBehind ViewMembers.aspx @@ -1506,35 +1321,30 @@ InsertAnchor.aspx - ASPXCodeBehind InsertAnchor.aspx insertChar.aspx - ASPXCodeBehind insertChar.aspx insertImage.aspx - ASPXCodeBehind insertImage.aspx insertLink.aspx - ASPXCodeBehind insertLink.aspx insertMacro.aspx - ASPXCodeBehind insertMacro.aspx @@ -1544,7 +1354,6 @@ tinymce3tinymceCompress.aspx - ASPXCodeBehind tinymce3tinymceCompress.aspx @@ -1554,35 +1363,30 @@ DictionaryItemList.aspx - ASPXCodeBehind DictionaryItemList.aspx EditDictionaryItem.aspx - ASPXCodeBehind EditDictionaryItem.aspx editLanguage.aspx - ASPXCodeBehind editLanguage.aspx EditMediaType.aspx - ASPXCodeBehind EditMediaType.aspx editScript.aspx - ASPXCodeBehind @@ -1606,7 +1410,6 @@ MacroContainerService.asmx - Component MediaUploader.ashx @@ -1616,7 +1419,6 @@ TreeClientService.asmx - Component @@ -1633,35 +1435,29 @@ default.aspx - ASPXCodeBehind default.aspx details.aspx - ASPXCodeBehind details.aspx preview.aspx - ASPXCodeBehind preview.aspx xml.aspx - ASPXCodeBehind xml.aspx - - ASPXCodeBehind - + @@ -1702,107 +1498,85 @@ XmlTree.xsd - - ASPXCodeBehind - + Code EditUser.aspx - ASPXCodeBehind EditUser.aspx EditUserType.aspx - ASPXCodeBehind EditUserType.aspx NodePermissions.ascx - ASPXCodeBehind NodePermissions.ascx PermissionEditor.aspx - ASPXCodeBehind PermissionEditor.aspx PermissionsHandler.asmx - Component webService.asmx - Component CacheRefresher.asmx - Component CheckForUpgrade.asmx - Component CMSNode.asmx - Component codeEditorSave.asmx - Component Developer.asmx - Component legacyAjaxCalls.asmx - Component nodeSorter.asmx - Component progressStatus.asmx - Component publication.asmx - Component Settings.asmx - Component templates.asmx - Component trashcan.asmx - Component UltimatePickerAutoCompleteHandler.ashx - - ASPXCodeBehind - + @@ -1860,13 +1634,9 @@ - - Component - + - - Component - + diff --git a/src/Umbraco.Web/WebApi/UmbracoApiController.cs b/src/Umbraco.Web/WebApi/UmbracoApiController.cs index 6462988827..e4c2b4a57e 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiController.cs @@ -1,8 +1,14 @@ using System; +using System.ComponentModel; +using System.Linq; using System.Web; using System.Web.Http; +using System.Web.Http.ModelBinding; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Validation; using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi {