diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 7dfeb716e3..93960d70c4 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -21,6 +21,8 @@ namespace Umbraco.Core.Models internal static bool HasPathAccess(string path, int startNodeId, int recycleBinId) { + Mandate.ParameterNotNullOrEmpty(path, "path"); + var formattedPath = "," + path + ","; var formattedStartNodeId = "," + startNodeId.ToInvariantString() + ","; var formattedRecycleBinId = "," + recycleBinId.ToInvariantString() + ","; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index e1fe4afe79..a7bf00954b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -564,6 +564,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { publish: function (content, isNew, files) { return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); }, + + sendToPublish: function (content, isNew, files) { + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); + }, publishById: function(id){ 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 98cec6bb6b..057a88721a 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 @@ -73,7 +73,7 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, cont return { letter: ch, labelKey: "buttons_saveToPublish", - handler: $scope.saveAndPublish, + handler: $scope.sendToPublish, hotKey: "ctrl+t" }; case "A": @@ -96,6 +96,45 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, cont } } + /** This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish */ + function performSave(args) { + var deferred = $q.defer(); + + if (formHelper.submitForm({ scope: $scope, statusMessage: args.statusMessage })) { + + args.saveMethod($scope.content, $routeParams.create, fileManager.getFiles()) + .then(function (data) { + + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); + + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + newContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + + configureButtons(data); + + navigationService.syncPath(data.path.split(","), true); + + deferred.resolve(data); + + }, function (err) { + contentEditingHelper.handleSaveError({ + err: err, + allNewProps: contentEditingHelper.getAllProps(err.data), + allOrigProps: contentEditingHelper.getAllProps($scope.content) + }); + + deferred.reject(err); + }); + } + else { + deferred.reject(); + } + + return deferred.promise; + } if ($routeParams.create) { //we are creating so get an empty content item @@ -151,36 +190,16 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, cont }; + $scope.sendToPublish = function() { + return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending..." }); + }; + $scope.saveAndPublish = function() { + return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing..." }); + }; - if (formHelper.submitForm({ scope: $scope, statusMessage: "Publishing..." })) { - - contentResource.publish($scope.content, $routeParams.create, fileManager.getFiles()) - .then(function(data) { - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - newContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) - }); - - configureButtons(data); - - navigationService.syncPath(data.path.split(","), true); - - }, function(err) { - - contentEditingHelper.handleSaveError({ - err: err, - redirectOnFailure: true, - allNewProps: contentEditingHelper.getAllProps(err.data), - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) - }); - }); - } - + $scope.save = function () { + return performSave({ saveMethod: contentResource.save, statusMessage: "Saving..." }); }; $scope.preview = function(content){ @@ -192,46 +211,7 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, cont $window.open('dialogs/preview.aspx?id='+content.id,'umbpreview'); } }; - - $scope.save = function() { - var deferred = $q.defer(); - - if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - - contentResource.save($scope.content, $routeParams.create, fileManager.getFiles()) - .then(function(data) { - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - newContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) - }); - - configureButtons(data); - - //fetch tree - navigationService.syncPath(data.path.split(","), true); - - deferred.resolve(data); - }, function(err) { - contentEditingHelper.handleSaveError({ - err: err, - allNewProps: contentEditingHelper.getAllProps(err.data), - allOrigProps: contentEditingHelper.getAllProps($scope.content) - }); - - deferred.reject(err); - }); - } - else { - deferred.reject(); - } - - return deferred.promise; - }; - + /** this method is called for all action buttons and then we proxy based on the btn definition */ $scope.performAction = function(btn) { if (!btn || !angular.isFunction(btn.handler)) { diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 961a452180..06cf0ec417 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -209,10 +209,9 @@ namespace Umbraco.Web.Editors // then we cannot continue saving, we can only display errors // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display // a message indicating this - if (!ModelState.IsValid) + if (ModelState.IsValid == false) { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) - && (contentItem.Action == ContentSaveAction.SaveNew || contentItem.Action == ContentSaveAction.PublishNew)) + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the modelstate to the outgoing object and throw a validation message @@ -242,6 +241,10 @@ namespace Umbraco.Web.Editors //save the item Services.ContentService.Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); } + else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) + { + throw new NotSupportedException("Send to publish is currently not supported"); + } else { //publish the item and check if it worked, if not we will show a diff msg below @@ -262,9 +265,13 @@ namespace Umbraco.Web.Editors case ContentSaveAction.SaveNew: display.AddSuccessNotification(ui.Text("speechBubbles", "editContentSavedHeader"), ui.Text("speechBubbles", "editContentSavedText")); break; + case ContentSaveAction.SendPublish: + case ContentSaveAction.SendPublishNew: + display.AddSuccessNotification(ui.Text("speechBubbles", "editContentSendToPublish"), ui.Text("speechBubbles", "editContentSendToPublishText")); + break; case ContentSaveAction.Publish: case ContentSaveAction.PublishNew: - ShowMessageForStatus(publishStatus.Result, display); + ShowMessageForPublishStatus(publishStatus.Result, display); break; } @@ -553,7 +560,7 @@ namespace Umbraco.Web.Editors return toMove; } - private void ShowMessageForStatus(PublishStatus status, ContentItemDisplay display) + private void ShowMessageForPublishStatus(PublishStatus status, ContentItemDisplay display) { switch (status.StatusType) { diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 4d8add3866..77125cc4b9 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -159,5 +159,15 @@ namespace Umbraco.Web.Editors : (TPersisted) Request.Properties[typeof (TPersisted).ToString()]; } + /// + /// Returns true if the action passed in means we need to create something new + /// + /// + /// + internal static bool IsCreatingAction(ContentSaveAction action) + { + return (action.ToString().EndsWith("New")); + } + } } diff --git a/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs b/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs index 24b684d9f7..62898e7889 100644 --- a/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs @@ -52,6 +52,11 @@ namespace Umbraco.Web.Editors get { return _userService ?? ApplicationContext.Current.Services.UserService; } } + public override bool AllowMultiple + { + get { return true; } + } + public override void OnActionExecuting(HttpActionContext actionContext) { var contentItem = (ContentItemSave)actionContext.ActionArguments["contentItem"]; @@ -75,8 +80,31 @@ namespace Umbraco.Web.Editors contentToCheck = contentItem.PersistedContent; contentIdToCheck = contentToCheck.Id; break; + case ContentSaveAction.SendPublish: + permissionToCheck.Add(ActionToPublish.Instance.Letter); + contentToCheck = contentItem.PersistedContent; + contentIdToCheck = contentToCheck.Id; + break; case ContentSaveAction.SaveNew: + //Save new requires both ActionNew AND ActionUpdate + permissionToCheck.Add(ActionNew.Instance.Letter); + permissionToCheck.Add(ActionUpdate.Instance.Letter); + if (contentItem.ParentId != Constants.System.Root) + { + contentToCheck = ContentService.GetById(contentItem.ParentId); + contentIdToCheck = contentToCheck.Id; + } + else + { + contentIdToCheck = contentItem.ParentId; + } + break; + case ContentSaveAction.SendPublishNew: + //Send new requires both ActionToPublish AND ActionUpdate + + permissionToCheck.Add(ActionNew.Instance.Letter); + permissionToCheck.Add(ActionToPublish.Instance.Letter); if (contentItem.ParentId != Constants.System.Root) { contentToCheck = ContentService.GetById(contentItem.ParentId); diff --git a/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs b/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs index 31a581eac2..ed26fcbc57 100644 --- a/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs @@ -71,8 +71,6 @@ namespace Umbraco.Web.Editors } break; - case ContentSaveAction.Publish: - case ContentSaveAction.PublishNew: default: //we don't support this for media actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.NotFound); diff --git a/src/Umbraco.Web/Editors/MembershipProviderValidationFilterAttribute.cs b/src/Umbraco.Web/Editors/MembershipProviderValidationFilterAttribute.cs index f25a833b7d..714074861f 100644 --- a/src/Umbraco.Web/Editors/MembershipProviderValidationFilterAttribute.cs +++ b/src/Umbraco.Web/Editors/MembershipProviderValidationFilterAttribute.cs @@ -74,8 +74,6 @@ namespace Umbraco.Web.Editors return false; } break; - case ContentSaveAction.Publish: - case ContentSaveAction.PublishNew: default: //we don't support this for members throw new HttpResponseException(HttpStatusCode.NotFound); @@ -120,8 +118,6 @@ namespace Umbraco.Web.Editors return false; } break; - case ContentSaveAction.Publish: - case ContentSaveAction.PublishNew: default: //we don't support this for members throw new HttpResponseException(HttpStatusCode.NotFound); diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs b/src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs index 337c97e12b..ed54588ccc 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs @@ -11,7 +11,7 @@ Save = 0, /// - /// Saves a new content item + /// Creates a new content item /// SaveNew = 1, @@ -21,8 +21,18 @@ Publish = 2, /// - /// Saves an publishes a new content item + /// Creates and publishes a new content item /// - PublishNew = 3 + PublishNew = 3, + + /// + /// Saves and sends publish notification + /// + SendPublish = 4, + + /// + /// Creates and sends publish notification + /// + SendPublishNew = 5 } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs index 6e415b0d43..df9a76cda8 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs @@ -18,6 +18,7 @@ using Newtonsoft.Json.Serialization; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models; +using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Security; using Umbraco.Web.WebApi.Filters; @@ -163,16 +164,16 @@ namespace Umbraco.Web.WebApi.Binders }); } - if (model.Action == ContentSaveAction.Publish || model.Action == ContentSaveAction.Save) - { - //finally, let's lookup the real content item and create the DTO item - model.PersistedContent = GetExisting(model); - } - else + if (ContentControllerBase.IsCreatingAction(model.Action)) { //we are creating new content model.PersistedContent = CreateNew(model); } + else + { + //finally, let's lookup the real content item and create the DTO item + model.PersistedContent = GetExisting(model); + } //create the dto from the persisted model if (model.PersistedContent != null)