diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs
index d551421c00..ff270458bb 100644
--- a/src/Umbraco.Core/Models/IContent.cs
+++ b/src/Umbraco.Core/Models/IContent.cs
@@ -116,7 +116,7 @@ namespace Umbraco.Core.Models
/// A value indicating whether the values could be published.
///
/// The document must then be published via the content service.
- /// Values are not published if they are not valie.
+ /// Values are not published if they are not valid.
///
bool PublishValues(int? languageId = null, string segment = null);
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
index 1e168973ad..4865ffa88a 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
@@ -197,11 +197,10 @@
// This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
function performSave(args) {
- var deferred = $q.defer();
-
+
$scope.page.buttonGroupState = "busy";
- contentEditingHelper.contentEditorPerformSave({
+ return contentEditingHelper.contentEditorPerformSave({
statusMessage: args.statusMessage,
saveMethod: args.saveMethod,
scope: $scope,
@@ -214,7 +213,6 @@
$scope.page.buttonGroupState = "success";
- deferred.resolve(data);
}, function (err) {
//error
if (err) {
@@ -222,11 +220,8 @@
}
$scope.page.buttonGroupState = "error";
-
- deferred.reject(err);
+ return $q.reject(err);
});
-
- return deferred.promise;
}
function resetLastListPageNumber(content) {
@@ -304,15 +299,19 @@
// return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" });
var dialog = {
- title: "Ready to Publish?",
+ title: "Ready to Publish?", //TODO: localize
view: "publish",
+ variants: $scope.editors[0].content.variants,
submitButtonLabel: "Publish",
submit: function(model) {
model.submitButtonState = "busy";
- console.log(model.selection);
- // TODO: call bulk publishing method
- performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" }).then(function(){
+
+ //we need to return this promise so that the dialog can handle the result and wire up the validation response
+ return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" }).then(function(){
overlayService.close();
+ }, function(err) {
+ model.submitButtonState = "error";
+ return $q.reject(err);
});
},
close: function(oldModel) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js
index 98124887bc..25d324408d 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js
@@ -412,7 +412,7 @@ Opens an overlay to show a custom YSOD.
(function() {
'use strict';
- function OverlayDirective($timeout, formHelper, overlayHelper, localizationService) {
+ function OverlayDirective($timeout, formHelper, overlayHelper, localizationService, $q) {
function link(scope, el, attr, ctrl) {
@@ -638,14 +638,21 @@ Opens an overlay to show a custom YSOD.
scope.submitForm = function(model) {
if(scope.model.submit) {
if (formHelper.submitForm({scope: scope})) {
- formHelper.resetForm({ scope: scope });
-
- if(scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) {
- scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton);
- } else {
- unregisterOverlay();
- scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton);
- }
+
+ if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) {
+ //wrap in a when since we don't know if this is a promise or not
+ $q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then(
+ function() {
+ formHelper.resetForm({ scope: scope });
+ }, angular.noop);
+ } else {
+ unregisterOverlay();
+ //wrap in a when since we don't know if this is a promise or not
+ $q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then(
+ function() {
+ formHelper.resetForm({ scope: scope });
+ }, angular.noop);
+ }
}
}
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 4c5cb2abb1..0a586be540 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
@@ -54,13 +54,11 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//we will use the default one for content if not specified
var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback;
- var deferred = $q.defer();
-
if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage, action: args.action })) {
args.scope.busy = true;
- args.saveMethod(args.content, $routeParams.create, fileManager.getFiles())
+ return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles())
.then(function (data) {
formHelper.resetForm({ scope: args.scope, notifications: data.notifications });
@@ -74,7 +72,6 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
});
args.scope.busy = false;
- deferred.resolve(data);
}, function (err) {
self.handleSaveError({
@@ -91,14 +88,13 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
}
}
args.scope.busy = false;
- deferred.reject(err);
+ return $q.reject(err);
});
}
else {
- deferred.reject();
+ return angularHelper.rejectedPromise();
}
-
- return deferred.promise;
+
},
/** Used by the content editor and media editor to add an info tab to the tabs array (normally known as the properties tab) */
@@ -508,7 +504,6 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
* @description
* A function to handle what happens when we have validation issues from the server side
*
- * TODO: Move to formHelper, so all this is in one place
*/
handleSaveError: function (args) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js
index a6b77ce65f..439720e461 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js
@@ -324,15 +324,21 @@
//this is basically the same as for media but we need to explicitly add some extra properties
var saveModel = this.formatMediaPostData(displayModel, action);
- //get the selected variant
- var currVariant = _.find(displayModel.variants,
- function(v) {
- return v.current === true;
+ //get the selected variant and build the additional published variants
+ saveModel.publishVariations = [];
+ _.each(displayModel.variants,
+ function (d) {
+ //set the selected variant if this is current
+ if (d.current === true) {
+ saveModel.languageId = d.language.id;
+ }
+ if (d.publish === true) {
+ saveModel.publishVariations.push({
+ languageId: d.language.id,
+ segment: d.segment
+ });
+ }
});
- if (currVariant) {
- saveModel.languageId = currVariant.language.id;
- }
-
var propExpireDate = displayModel.removeDate;
var propReleaseDate = displayModel.releaseDate;
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/publish/publish.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/publish/publish.controller.js
index 45a2856066..0c07bad316 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/publish/publish.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/publish/publish.controller.js
@@ -4,54 +4,14 @@
function PublishController($scope, $timeout) {
var vm = this;
- vm.variants = [];
- vm.loading = true;
+ vm.variants = $scope.model.variants;
function onInit() {
-
- vm.loading = true;
-
- $timeout(function(){
-
- vm.variants = [
- {
- "cultureDisplayName": "English (United States)",
- "culture": "en-US",
- "state": "Published (pending changes)",
- "selected": false,
- "validationError": false,
- "validationErrorMessage": ""
- },
- {
- "cultureDisplayName": "Spanish (Spain)",
- "culture": "es-ES",
- "state": "Draft",
- "selected": false,
- "validationError": false,
- "validationErrorMessage": ""
- },
- {
- "cultureDisplayName": "French (France)",
- "culture": "fr-FR",
- "state": "Published (pending changes)",
- "selected": false,
- "validationError": true,
- "validationErrorMessage": "Lorem ipsum dolor sit amet..."
- },
- {
- "cultureDisplayName": "German (Germany)",
- "culture": "de-DE",
- "state": "Draft",
- "selected": false,
- "validationError": false,
- "validationErrorMessage": ""
- }
- ];
-
- vm.loading = false;
-
- }, 1000);
-
+ _.each(vm.variants,
+ function (v) {
+ v.compositeId = v.language.id + "_" + (v.segment ? v.segment : "");
+ v.htmlId = "publish_variant_" + v.compositeId;
+ });
}
onInit();
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/publish/publish.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/publish/publish.html
index 5f67c90270..70cfe83a53 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/publish/publish.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/publish/publish.html
@@ -1,4 +1,4 @@
-
+
What languages would you like to publish?
@@ -10,20 +10,24 @@
-
+
+
+
-
\ No newline at end of file
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js
index ce6df0d351..7ac9c340f5 100644
--- a/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js
@@ -73,10 +73,15 @@
function save() {
- if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
+ if (formHelper.submitForm({ scope: $scope })) {
vm.page.saveButtonState = "busy";
languageResource.save(vm.language).then(function (lang) {
+
+ formHelper.resetForm({
+ scope: $scope
+ });
+
vm.language = lang;
vm.page.saveButtonState = "success";
notificationsService.success(localizationService.localize("speechBubbles_languageSaved"));
@@ -86,10 +91,7 @@
}, function (err) {
vm.page.saveButtonState = "error";
- contentEditingHelper.handleSaveError({
- redirectOnFailure: false,
- err: err
- });
+ formHelper.handleError(err);
//show any notifications
if (angular.isArray(err.data.notifications)) {
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
index 011a07012a..04b417124b 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
@@ -1418,8 +1418,8 @@ To manage your website, simply open the Umbraco back office and start adding con
An error occurred while unlocking the userMember was exported to fileAn error occurred while exporting the member
- Validation failed
- Publishing was interrupted because validation failed for required content language '%0%'
+ Validation failed for mandatory language '%0%'
+ Validation failed for language '%0%'Uses CSS syntax ex: h1, .redHeader, .blueTex
diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs
index 171ff9603b..1e6d0867d4 100644
--- a/src/Umbraco.Web/Editors/ContentController.cs
+++ b/src/Umbraco.Web/Editors/ContentController.cs
@@ -29,6 +29,7 @@ using Umbraco.Web.Models;
using Umbraco.Web._Legacy.Actions;
using Constants = Umbraco.Core.Constants;
using ContentVariation = Umbraco.Core.Models.ContentVariation;
+using Language = Umbraco.Web.Models.ContentEditing.Language;
namespace Umbraco.Web.Editors
{
@@ -556,48 +557,7 @@ namespace Umbraco.Web.Editors
}
return contentItemDisplay;
}
-
- ///
- /// This will check content variants other than the current one for validation errors
- ///
- ///
- ///
- ///
- ///
- /// When publishing a content variant, we need to ensure that:
- /// a) the current variant passes validation rules
- /// b) the default variant and any mandatory variants pass validation rules
- ///
- private bool ValidateContentVariants(IContent content, ContentItemDto dto, ContentItemDisplay display)
- {
- var variants = display.Variants.ToList();
- if (variants.Count == 1) return true; // no need to validate others
-
- var validator = new ContentItemValidationHelper();
-
- var currVariant = display.Variants.First(x => x.IsCurrent);
-
- var isValid = true;
-
- //for each variant, we'll need to re-map to a display object to validate it's properties
- foreach (var contentVariation in variants.Where(x => x.Mandatory && x.Language.Id != currVariant.Language.Id))
- {
- var mapped = MapToBasic(content, contentVariation.Language.Id);
- var modelState = new ModelStateDictionary(); //new dictionary since we don't want validation results populating the current model state (at least for now)
- var variantValid = validator.ValidatePropertyData(mapped, dto, modelState);
- if (!variantValid)
- {
- display.AddErrorNotification(
- Services.TextService.Localize("speechBubbles/contentVariantValidationErrorHeader"),
- Services.TextService.Localize("speechBubbles/contentLanguageValidationErrorText", new[]{contentVariation.Language.Name}));
-
- isValid = false;
- }
- }
-
- return isValid;
- }
-
+
private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod)
{
//If we've reached here it means:
@@ -657,18 +617,41 @@ namespace Umbraco.Web.Editors
else
{
//publish the item and check if it worked, if not we will show a diff msg below
- contentItem.PersistedContent.PublishValues(contentItem.LanguageId);
+ contentItem.PersistedContent.PublishValues(contentItem.LanguageId); //we are not checking for a return value here because we've alraedy pre-validated the property values
+
+ //check if we are publishing other variants and validate them
+ var allLangs = Services.LocalizationService.GetAllLanguages().ToList();
+ var variantsToValidate = contentItem.PublishVariations.Where(x => x.LanguageId != contentItem.LanguageId).ToList();
+ foreach (var publishVariation in variantsToValidate)
+ {
+ if (!contentItem.PersistedContent.PublishValues(publishVariation.LanguageId))
+ {
+ var errMsg = Services.TextService.Localize("speechBubbles/contentLangValidationError", new[]{allLangs.First(x => x.Id == publishVariation.LanguageId).CultureName});
+ ModelState.AddModelError("publish_variant_" + publishVariation.LanguageId + "_", errMsg);
+ }
+ }
+
+ //validate any mandatory variants that are not in the list
+ var mandatoryLangs = Mapper.Map, IEnumerable>(allLangs)
+ .Where(x => variantsToValidate.All(v => v.LanguageId != x.Id)) //don't include variants above
+ .Where(x => x.Id != contentItem.LanguageId) //don't include the current variant
+ .Where(x => x.Mandatory);
+ foreach (var lang in mandatoryLangs)
+ {
+ if (contentItem.PersistedContent.Validate(lang.Id).Length > 0)
+ {
+ var errMsg = Services.TextService.Localize("speechBubbles/contentReqLangValidationError", new[]{allLangs.First(x => x.Id == lang.Id).CultureName});
+ ModelState.AddModelError("publish_variant_" + lang.Id + "_", errMsg);
+ }
+ }
+
publishStatus = Services.ContentService.SaveAndPublish(contentItem.PersistedContent, Security.CurrentUser.Id);
wasCancelled = publishStatus.Result == PublishResultType.FailedCancelledByEvent;
}
- //return the updated model
+ //get the updated model
var display = MapToDisplay(contentItem.PersistedContent, contentItem.LanguageId);
-
- //validate the mandatory variants
- if (!ValidateContentVariants(contentItem.PersistedContent, contentItem.ContentDto, display))
- throw new HttpResponseException(Request.CreateValidationErrorResponse(display));
-
+
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
HandleInvalidModelState(display);
diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs
index d8ab8149dc..ca410100ec 100644
--- a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs
+++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Umbraco.Core.Models;
@@ -15,7 +16,7 @@ namespace Umbraco.Web.Models.ContentEditing
/// The language Id for the content variation being saved
///
[DataMember(Name = "languageId")]
- public int? LanguageId { get; set; }
+ public int? LanguageId { get; set; } //TODO: Change this to ContentVariationPublish, but this will all change anyways when we can edit all variants at once
///
/// The template alias to save
@@ -28,6 +29,11 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "expireDate")]
public DateTime? ExpireDate { get; set; }
-
+
+ ///
+ /// Indicates that these variations should also be published
+ ///
+ [DataMember(Name = "publishVariations")]
+ public IEnumerable PublishVariations { get; set; }
}
}
diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs
index 9079395ca2..83fb3b03f3 100644
--- a/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs
+++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Umbraco.Core.Models;
@@ -10,18 +11,19 @@ namespace Umbraco.Web.Models.ContentEditing
[DataContract(Name = "contentVariant", Namespace = "")]
public class ContentVariation
{
+ [DataMember(Name = "language", IsRequired = true)]
+ [Required]
+ public Language Language { get; set; }
+
+ [DataMember(Name = "segment")]
+ public string Segment { get; set; }
+
///
/// The content name of the variant
///
[DataMember(Name = "name")]
public string Name { get; set; }
- [DataMember(Name = "language")]
- public Language Language { get; set; }
-
- [DataMember(Name = "segment")]
- public string Segment { get; set; }
-
[DataMember(Name = "state")]
public string PublishedState { get; set; }
diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariationPublish.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariationPublish.cs
new file mode 100644
index 0000000000..aefc487e7c
--- /dev/null
+++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariationPublish.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Web.Models.ContentEditing
+{
+ ///
+ /// Used to indicate which additional variants to publish when a content item is published
+ ///
+ public class ContentVariationPublish
+ {
+ [DataMember(Name = "languageId", IsRequired = true)]
+ [Required]
+ public int LanguageId { get; set; }
+
+ [DataMember(Name = "segment")]
+ public string Segment { get; set; }
+ }
+}
diff --git a/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs b/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs
index a387876a45..dcd4a4f037 100644
--- a/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs
+++ b/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
diff --git a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs
index dd229f47e3..1ced557b91 100644
--- a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs
+++ b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs
@@ -32,7 +32,8 @@ namespace Umbraco.Web.Models.Mapping
//fixme these all need to the variant values but we need to wait for the db/service changes
Name = source.Name ,
Exists = source.HasVariation(x.Id), //TODO: This needs to be wired up with new APIs when they are ready
- PublishedState = source.PublishedState.ToString()
+ PublishedState = source.PublishedState.ToString(),
+ //Segment = ?? We'll need to populate this one day when we support segments
}).ToList();
var langId = context.GetLanguageId();
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 405cd8f577..14603da9eb 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -230,6 +230,7 @@
+
diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
index 77eb7d7f8a..0a399c24c5 100644
--- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
+++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
@@ -27,9 +27,14 @@ namespace Umbraco.Web.WebApi.Binders
protected override ContentItemDto MapFromPersisted(ContentItemSave model)
{
- return ContextMapper.Map>(model.PersistedContent, new Dictionary
+ return MapFromPersisted(model.PersistedContent, model.LanguageId);
+ }
+
+ internal static ContentItemDto MapFromPersisted(IContent content, int? languageId)
+ {
+ return ContextMapper.Map>(content, new Dictionary
{
- [ContextMapper.LanguageKey] = model.LanguageId
+ [ContextMapper.LanguageKey] = languageId
});
}
}
diff --git a/src/Umbraco.Web/WebServices/BulkPublishController.cs b/src/Umbraco.Web/WebServices/BulkPublishController.cs
index c2f888527e..daea5cc58f 100644
--- a/src/Umbraco.Web/WebServices/BulkPublishController.cs
+++ b/src/Umbraco.Web/WebServices/BulkPublishController.cs
@@ -29,7 +29,7 @@ namespace Umbraco.Web.WebServices
if (publishDescendants == false)
{
- content.PublishValues(); // fixme variants?
+ content.PublishValues(); // fixme variants? validation - when this returns null?
var result = Services.ContentService.SaveAndPublish(content, Security.CurrentUser.Id);
return Json(new
{