diff --git a/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs b/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs index 0ea65415c5..90704648d1 100644 --- a/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Publishing public abstract class BasePublishingStrategy : IPublishingStrategy { - protected internal abstract Attempt PublishInternal(IContent content, int userId); + internal abstract Attempt PublishInternal(IContent content, int userId); /// /// Publishes a list of content items @@ -23,10 +23,10 @@ namespace Umbraco.Core.Publishing /// /// If true this will validate each content item before trying to publish it, if validation fails it will not be published. /// - protected internal abstract IEnumerable> PublishWithChildrenInternal( + internal abstract IEnumerable> PublishWithChildrenInternal( IEnumerable content, int userId, bool includeUnpublishedDocuments = true, bool validateContent = false); - protected internal abstract IEnumerable> UnPublishInternal(IEnumerable content, int userId); + internal abstract IEnumerable> UnPublishInternal(IEnumerable content, int userId); public abstract bool Publish(IContent content, int userId); public abstract bool PublishWithChildren(IEnumerable content, int userId); diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Publishing/PublishStatus.cs index e406c3fff5..6bbaa59783 100644 --- a/src/Umbraco.Core/Publishing/PublishStatus.cs +++ b/src/Umbraco.Core/Publishing/PublishStatus.cs @@ -5,10 +5,10 @@ namespace Umbraco.Core.Publishing /// /// The result of publishing a content item /// - public class PublishStatus + internal class PublishStatus { public IContent ContentItem { get; private set; } - public PublishStatusType StatusType { get; private set; } + public PublishStatusType StatusType { get; internal set; } public PublishStatus(IContent content, PublishStatusType statusType) { diff --git a/src/Umbraco.Core/Publishing/PublishStatusType.cs b/src/Umbraco.Core/Publishing/PublishStatusType.cs index 7eb8e19380..fd6cb128c3 100644 --- a/src/Umbraco.Core/Publishing/PublishStatusType.cs +++ b/src/Umbraco.Core/Publishing/PublishStatusType.cs @@ -3,43 +3,48 @@ namespace Umbraco.Core.Publishing /// /// A status type of the result of publishing a content item /// - public enum PublishStatusType + internal enum PublishStatusType { /// /// The publishing was successful. /// - Success, + Success = 0, + + /// + /// The item was already published + /// + SuccessAlreadyPublished = 1, /// /// The content could not be published because it's ancestor path isn't published /// - FailedPathNotPublished, + FailedPathNotPublished = 10, /// /// The content item was scheduled to be un-published and it has expired so we cannot force it to be /// published again as part of a bulk publish operation. /// - FailedHasExpired, + FailedHasExpired = 11, /// /// The content item is scheduled to be released in the future and therefore we cannot force it to /// be published during a bulk publish operation. /// - FailedAwaitingRelease, + FailedAwaitingRelease = 12, /// /// The content item is in the trash, it cannot be published /// - FailedIsTrashed, + FailedIsTrashed = 13, /// /// The publish action has been cancelled by an event handler /// - FailedCancelledByEvent, + FailedCancelledByEvent = 14, /// /// The content item contains invalid data (has not passed validation requirements) /// - FailedContentInvalid + FailedContentInvalid = 15 } } \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs index ef5b9f80f9..4a8cc5a63d 100644 --- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/PublishingStrategy.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Publishing /// /// to publish /// Id of the User issueing the publish operation - protected internal override Attempt PublishInternal(IContent content, int userId) + internal override Attempt PublishInternal(IContent content, int userId) { if (Publishing.IsRaisedEventCancelled(new PublishEventArgs(content), this)) { @@ -99,7 +99,7 @@ namespace Umbraco.Core.Publishing /// level and so on. If we detect that the above rule applies when the document publishing is cancelled we'll add it to the list of /// parentsIdsCancelled so that it's children don't get published. /// - protected internal override IEnumerable> PublishWithChildrenInternal( + internal override IEnumerable> PublishWithChildrenInternal( IEnumerable content, int userId, bool includeUnpublishedDocuments = true, bool validateContent = false) { var statuses = new List>(); @@ -114,6 +114,13 @@ namespace Umbraco.Core.Publishing // much difference because we iterate over them all anyways?? Morten? // Because we're grouping I think this will execute all the queries anyways so need to fetch it all first. var fetchedContent = content.ToArray(); + + //We're going to populate the statuses with all content that is already published because below we are only going to iterate over + // content that is not published. We'll set the status to "AlreadyPublished" + statuses.AddRange(fetchedContent.Where(x => x.Published) + .Select(x => new Attempt(true, new PublishStatus(x, PublishStatusType.SuccessAlreadyPublished)))); + + //group by level and iterate over each level (sorted ascending) var levelGroups = fetchedContent.GroupBy(x => x.Level); foreach (var level in levelGroups.OrderBy(x => x.Key)) { @@ -315,7 +322,7 @@ namespace Umbraco.Core.Publishing /// An enumerable list of /// Id of the User issueing the unpublish operation /// A list of publish statuses - protected internal override IEnumerable> UnPublishInternal(IEnumerable content, int userId) + internal override IEnumerable> UnPublishInternal(IEnumerable content, int userId) { var result = new List>(); diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index e75ee525a0..f329b33241 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -435,7 +435,8 @@ namespace Umbraco.Core.Services /// True if publishing succeeded, otherwise False public bool Publish(IContent content, int userId = 0) { - return SaveAndPublishDo(content, false, userId); + var result = SaveAndPublishDo(content, false, userId); + return result.Success; } /// @@ -473,7 +474,8 @@ namespace Umbraco.Core.Services /// True if publishing succeeded, otherwise False public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) { - return SaveAndPublishDo(content, false, userId, raiseEvents); + var result = SaveAndPublishDo(content, false, userId, raiseEvents); + return result.Success; } /// @@ -1026,7 +1028,7 @@ namespace Umbraco.Core.Services /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will not update the cache. /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False - internal bool Publish(IContent content, bool omitCacheRefresh = true, int userId = 0) + internal Attempt Publish(IContent content, bool omitCacheRefresh = true, int userId = 0) { return SaveAndPublishDo(content, omitCacheRefresh, userId); } @@ -1038,10 +1040,12 @@ namespace Umbraco.Core.Services /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will not update the cache. /// Optional Id of the User issueing the publishing /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published + /// If true this will validate the content before publishing /// True if publishing succeeded, otherwise False - internal IEnumerable> PublishWithChildren(IContent content, bool omitCacheRefresh = true, int userId = 0, bool includeUnpublished = false) + internal IEnumerable> PublishWithChildren( + IContent content, bool omitCacheRefresh = true, int userId = 0, bool includeUnpublished = false, bool validateContent = false) { - return PublishWithChildrenDo(content, omitCacheRefresh, userId, includeUnpublished); + return PublishWithChildrenDo(content, omitCacheRefresh, userId, includeUnpublished, validateContent); } /// @@ -1064,7 +1068,7 @@ namespace Umbraco.Core.Services /// Optional Id of the User issueing the publishing /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False - internal bool SaveAndPublish(IContent content, bool omitCacheRefresh = true, int userId = 0, bool raiseEvents = true) + internal Attempt SaveAndPublish(IContent content, bool omitCacheRefresh = true, int userId = 0, bool raiseEvents = true) { return SaveAndPublishDo(content, omitCacheRefresh, userId, raiseEvents); } @@ -1151,21 +1155,25 @@ namespace Umbraco.Core.Services return published; } - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache. - /// Optional Id of the User issueing the publishing - /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published - /// - /// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published - /// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that - /// are to be published. - /// - private IEnumerable> PublishWithChildrenDo(IContent content, bool omitCacheRefresh = false, int userId = 0, bool includeUnpublished = false) + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache. + /// Optional Id of the User issueing the publishing + /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published + /// If set to true will ensure the content is valid before publishing + /// + /// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published + /// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that + /// are to be published. + /// + private IEnumerable> PublishWithChildrenDo( + IContent content, bool omitCacheRefresh = false, int userId = 0, bool includeUnpublished = false, bool validateContent = false) { - var result = new List>(); + if (content == null) throw new ArgumentNullException("content"); + + var result = new List>(); //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published if (content.ParentId != -1 && content.ParentId != -20 && IsPublishable(content) == false) @@ -1197,14 +1205,16 @@ namespace Umbraco.Core.Services list.AddRange(GetDescendants(content)); //Publish and then update the database with new status - var publishedOutcome = _publishingStrategy.PublishWithChildrenInternal(list, userId, includeUnpublished).ToArray(); + var publishedOutcome = _publishingStrategy.PublishWithChildrenInternal(list, userId, includeUnpublished, validateContent).ToArray(); var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) { - //Only loop through content where the Published property has been updated - //foreach (var item in list.Where(x => ((ICanBeDirty)x).IsPropertyDirty("Published"))) - foreach (var item in publishedOutcome.Where(x => x.Success)) + //Only loop through content that was successfully published, was not already published and where the Published property has been updated + foreach (var item in publishedOutcome.Where( + x => x.Success + && x.Result.StatusType != PublishStatusType.SuccessAlreadyPublished + && ((ICanBeDirty) x.Result.ContentItem).IsPropertyDirty("Published"))) { item.Result.ContentItem.WriterId = userId; repository.AddOrUpdate(item.Result.ContentItem); @@ -1275,17 +1285,19 @@ namespace Umbraco.Core.Services /// Optional Id of the User issueing the publishing /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False - private bool SaveAndPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0, bool raiseEvents = true) + private Attempt SaveAndPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0, bool raiseEvents = true) { if(raiseEvents) { if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) - return false; + { + return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); + } } //Has this content item previously been published? If so, we don't need to refresh the children var previouslyPublished = HasPublishedVersion(content.Id); - var validForPublishing = true; + var publishStatus = new PublishStatus(content, PublishStatusType.Success); //initially set to success //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published if (content.ParentId != -1 && content.ParentId != -20 && IsPublishable(content) == false) @@ -1294,7 +1306,7 @@ namespace Umbraco.Core.Services string.Format( "Content '{0}' with Id '{1}' could not be published because its parent is not published.", content.Name, content.Id)); - validForPublishing = false; + publishStatus.StatusType = PublishStatusType.FailedPathNotPublished; } //Content contains invalid property values and can therefore not be published - fire event? @@ -1304,11 +1316,16 @@ namespace Umbraco.Core.Services string.Format( "Content '{0}' with Id '{1}' could not be published because of invalid properties.", content.Name, content.Id)); - validForPublishing = false; + publishStatus.StatusType = PublishStatusType.FailedContentInvalid; } //Publish and then update the database with new status - bool published = validForPublishing && _publishingStrategy.Publish(content, userId); + var publishResult = _publishingStrategy.PublishInternal(content, userId); + //set our publish status to the publish result + publishStatus.StatusType = publishResult.Result.StatusType; + + //we are successfully published if the flag is success (and for good measure we'll check the publish result) + bool published = publishStatus.StatusType == PublishStatusType.Success && publishResult.Success; var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) @@ -1361,7 +1378,9 @@ namespace Umbraco.Core.Services //Save xml to db and call following method to fire event through PublishingStrategy to update cache if (published && omitCacheRefresh == false) + { _publishingStrategy.PublishingFinalized(content); + } //We need to check if children and their publish state to ensure that we 'republish' content that was previously published if (published && omitCacheRefresh == false && previouslyPublished == false && HasChildren(content.Id)) @@ -1373,7 +1392,7 @@ namespace Umbraco.Core.Services Audit.Add(AuditTypes.Publish, "Save and Publish performed by user", userId, content.Id); - return published; + return new Attempt(publishStatus.StatusType == PublishStatusType.Success, publishStatus); } /// diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 6edddefe70..a46cc5916c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -653,6 +653,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 + + + If you just want to setup simple protection using a single login and password + + + diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js b/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js new file mode 100644 index 0000000000..04173f8749 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js @@ -0,0 +1,346 @@ +(function ($) { + + //extensions to base classes such as String and extension methods for jquery. + //NOTE: jquery must be loaded before this file. + + //create guid object on the window (which makes it global) + if (window.Guid == null) { + window.Guid = { + generate: function () { + ///generates a new Guid + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } + }; + } + + if (!window.__debug__) { + window.__debug__ = function (msg, category, isErr) { + ///global method to send debug statements to console that is cross browser (or at least checks if its possible) + + if (((typeof console) != "undefined") && console.log && console.error) { + if (isErr) console.error(category + ": " + msg); + else console.log(category + ": " + msg); + } + }; + } + + if (!String.prototype.startsWith) { + String.prototype.startsWith = function (str) { + ///startsWith extension method for string + + return this.substr(0, str.length) === str; + }; + } + + if (!String.prototype.endsWith) { + String.prototype.endsWith = function (str) { + ///endsWith extension method for string + + return this.substr(this.length - str.length) === str; + }; + } + + if (!String.prototype.utf8Encode) { + String.prototype.utf8Encode = function () { + ///UTF8 encoder for string + + var str = this.replace(/\r\n/g, "\n"); + var utftext = ""; + for (var n = 0; n < str.length; n++) { + var c = str.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + return utftext; + }; + } + + if (!String.prototype.utf8Decode) { + String.prototype.utf8Decode = function () { + var utftext = this; + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + + while (i < utftext.length) { + + c = utftext.charCodeAt(i); + + if (c < 128) { + string += String.fromCharCode(c); + i++; + } + else if ((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i + 1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = utftext.charCodeAt(i + 1); + c3 = utftext.charCodeAt(i + 2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + + } + + return string; + }; + } + + if (!String.prototype.base64Encode) { + String.prototype.base64Encode = function () { + ///Base64 encoder for string + + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + + var input = this.utf8Encode(); + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + keyStr.charAt(enc1) + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + keyStr.charAt(enc4); + + } + + return output; + }; + } + + if (!String.prototype.base64Decode) { + String.prototype.base64Decode = function () { + ///Base64 decoder for string + + var input = this; + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + } + + return output.utf8Decode(); + + }; + } + + + if (!Math.randomRange) { + Math.randomRange = function (from, to) { + ///randomRange extension for math + + return Math.floor(Math.random() * (to - from + 1) + from); + }; + } + + if (!String.prototype.toCamelCase) { + String.prototype.toCamelCase = function () { + ///toCamelCase extension method for string + + var s = this.toPascalCase(); + if ($.trim(s) == "") + return ""; + if (s.length > 1) { + var regex = /^([A-Z]*)([A-Z].*)/g; + if (s.match(regex)) { + var match = regex.exec(s); + s = match[1].toLowerCase() + match[2]; + s = s.substr(0, 1).toLowerCase() + s.substr(1); + } + } else { + s = s.toLowerCase(); + } + return s; + }; + } + + if (!String.prototype.toPascalCase) { + String.prototype.toPascalCase = function () { + ///toPascalCase extension method for string + + var s = ""; + $.each($.trim(this).split(/[\s\.-]+/g), function (idx, val) { + if ($.trim(val) == "") + return; + if (val.length > 1) + s += val.substr(0, 1).toUpperCase() + val.substr(1); + else + s += val.toUpperCase(); + }); + return s; + }; + } + + if (!String.prototype.toUmbracoAlias) { + String.prototype.toUmbracoAlias = function () { + //////toUmbracoAlias extension method for string + + var s = this.replace(/[^a-zA-Z0-9\s\.-]+/g, ''); // Strip none alphanumeric chars + return s.toCamelCase(); // Convert to camelCase + }; + } + + if (!String.prototype.toFunction) { + String.prototype.toFunction = function () { + var arr = this.split("."); + var fn = (window || this); + for (var i = 0, len = arr.length; i < len; i++) { + fn = fn[arr[i]]; + } + if (typeof fn !== "function") { + throw new Error("function not found"); + } + return fn; + }; + } + + //sets defaults for ajax + $.ajaxSetup({ + dataType: 'json', + cache: false, + contentType: 'application/json; charset=utf-8', + error: function (x, t, e) { + if (x.status.toString().startsWith("500")) { + $u.Sys.ApiMgr.getApp().handleAjaxException(x, t, e); + } + } + }); + + $.fn.getAllAttributes = function () { + ///extension method to get all attributes of a selected element + + if ($(this).length != 1) { + throw "the getAllAttributes method can only be called when matching one jQuery selector"; + }; + var el = $(this).get(0); + var arr = []; + for (var i = 0, attrs = el.attributes; i < attrs.length; i++) { + arr.push({ name: attrs.item(i).nodeName, value: attrs.item(i).nodeValue }); + } + return arr; + }; + + $.fn.outerHtml = function () { + ///extension to get the 'outer html' of an element + + if ($(this).length != 1) { + throw "the getAllAttributes method can only be called when matching one jQuery selector"; + }; + var nodeName = $(_opts.content).get(0).nodeName.toLowerCase(); + //start creating raw html + var outerHtml = "<" + nodeName; + //get all the attributes/values from the original element and add them to the new one + var allAttributes = $(_opts.content).getAllAttributes(); + for (var a in allAttributes) { + outerHtml += " " + allAttributes[a].name + "='" + allAttributes[a].value + "'"; + } + outerHtml += ">"; + outerHtml += $(_opts.content).html(); + outerHtml += ""; + return outerHtml; + }; + + $.fn.focusFirst = function () { + ///extension to focus the first editable field in a form + + return $(this).each(function () { + if ($(this).get(0).nodeName.toLowerCase() != "form") { + throw "The focusFirst method can only be applied to a form element"; + } + var first = $(this).find(":input:enabled:visible").not(":submit").not(":button").not(":file").not(":image").not(":radio"); + if (first.length > 0) { + $(first[0]).focus(); + } + }); + }; + + $.fn.getAttributes = function () { + ///Extension method to return all of the attributes for an element + + var attributes = []; + + if (!this.length) + return this; + + $.each(this[0].attributes, function (index, attr) { + attributes.push({ name: attr.name, value: attr.value }); + }); + + return attributes; + }; + + + //defaults that need to be set on ready + $(document).ready(function () { + + //adds a default ignore parameter to jquery validation + if ($.validator) { + $.validator.setDefaults({ ignore: ".ignore" }); + } + + //adds a "re-parse" method to the Unobtrusive JS framework since Parse doesn't actually reparse + if ($.validator && $.validator.unobtrusive) { + $.validator.unobtrusive.reParse = function ($selector) { + $selector.removeData("validator"); + $.validator.unobtrusive.parse($selector); + }; + } + + }); + +})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.css b/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.css index 3e0aa5e809..396abcd8e6 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.css +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.css @@ -20,4 +20,9 @@ #feedbackMsg > div { padding: 5px; +} + +#feedbackMsg ul { + margin: 0; + padding-left: 15px; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js b/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js index 3995c9165b..f361ec39a1 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js @@ -6,6 +6,7 @@ //private methods/variables _opts: null, + _koViewModel: null, // Constructor constructor: function () { @@ -24,11 +25,13 @@ var self = this; //The knockout js view model for the selected item - var koViewModel = { + self._koViewModel = { publishAll: ko.observable(false), includeUnpublished: ko.observable(false), processStatus: ko.observable("init"), isSuccessful: ko.observable(false), + resultMessages: ko.observableArray(), + resultMessage: ko.observable(""), //if there's only one result message closeDialog: function () { UmbClientMgr.closeModalWindow(); }, @@ -37,25 +40,34 @@ $.post(self._opts.restServiceLocation + "PublishDocument", JSON.stringify({ - documentId: self._opts.documentId + documentId: self._opts.documentId, + publishDescendants: self._koViewModel.publishAll(), + includeUnpublished: self._koViewModel.includeUnpublished() }), function (e) { - if (e.success) { - self.submitSuccess(e.message, e.header); - } else { - self.submitFailure(e.message, e.header); + self._koViewModel.processStatus("complete"); + self._koViewModel.isSuccessful(e.success); + var msgs = e.message.trim().split("\r\n"); + if (msgs.length > 1) { + for (var m in msgs) { + self._koViewModel.resultMessages.push({ message: msgs[m] }); + } } + else { + self._koViewModel.resultMessage(msgs[0]); + } + }); } }; //ensure includeUnpublished is always false if publishAll is ever false - koViewModel.publishAll.subscribe(function (newValue) { + self._koViewModel.publishAll.subscribe(function (newValue) { if (newValue === false) { - koViewModel.includeUnpublished(false); + self._koViewModel.includeUnpublished(false); } }); - ko.applyBindings(koViewModel); + ko.applyBindings(self._koViewModel); } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index bf6848f623..2a468b0059 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1730,6 +1730,7 @@ True Reference.map + diff --git a/src/Umbraco.Web/WebServices/BulkPublishController.cs b/src/Umbraco.Web/WebServices/BulkPublishController.cs new file mode 100644 index 0000000000..725b734c13 --- /dev/null +++ b/src/Umbraco.Web/WebServices/BulkPublishController.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Web.Mvc; +using umbraco; + +namespace Umbraco.Web.WebServices +{ + /// + /// A REST controller used for the publish dialog in order to publish bulk items at once + /// + public class BulkPublishController : UmbracoAuthorizedController + { + /// + /// Publishes an document + /// + /// + /// true to publish descendants as well + /// true to publish documents that are unpublished + /// A Json array containing objects with the child id's of the document and it's current published status + [HttpPost] + public JsonResult PublishDocument(int documentId, bool publishDescendants, bool includeUnpublished) + { + var doc = Services.ContentService.GetById(documentId); + var contentService = (ContentService) Services.ContentService; + if (!publishDescendants) + { + var result = contentService.SaveAndPublish(doc, false); + return Json(new + { + success = result.Success, + message = GetMessageForStatus(result.Result) + }); + } + else + { + var result = ((ContentService) Services.ContentService) + .PublishWithChildren(doc, false, UmbracoUser.Id, includeUnpublished, true) + .ToArray(); + return Json(new + { + success = result.All(x => x.Success), + message = GetMessageForStatuses(result.Select(x => x.Result), doc) + }); + } + } + + private string GetMessageForStatuses(IEnumerable statuses, IContent doc) + { + //if all are successful then just say it was successful + if (statuses.All(x => ((int) x.StatusType) < 10)) + { + return ui.Text("publish", "nodePublishAll", doc.Name, UmbracoUser); + } + + //if they are not all successful the we'll add each error message to the output (one per line) + var sb = new StringBuilder(); + foreach (var msg in statuses + .Where(x => ((int)x.StatusType) >= 10) + .Select(GetMessageForStatus) + .Where(msg => !msg.IsNullOrWhiteSpace())) + { + sb.AppendLine(msg.Trim()); + } + return sb.ToString(); + } + + private string GetMessageForStatus(PublishStatus status) + { + switch (status.StatusType) + { + case PublishStatusType.Success: + case PublishStatusType.SuccessAlreadyPublished: + return ui.Text("publish", "nodePublish", status.ContentItem.Name, UmbracoUser); + case PublishStatusType.FailedPathNotPublished: + return ui.Text("publish", "contentPublishedFailedByParent", + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), UmbracoUser); + case PublishStatusType.FailedHasExpired: + case PublishStatusType.FailedAwaitingRelease: + case PublishStatusType.FailedIsTrashed: + return ""; //we will not notify about this type of failure... or should we ? + case PublishStatusType.FailedCancelledByEvent: + return ui.Text("publish", "contentPublishedFailedByEvent", + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), UmbracoUser); + case PublishStatusType.FailedContentInvalid: + return ui.Text("publish", "contentPublishedFailedInvalid", + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), UmbracoUser); + default: + return status.StatusType.ToString(); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 8ecb0a9f11..7ab39660ff 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -1,49 +1,21 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Web.Mvc; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Services; using Umbraco.Web.Cache; using Umbraco.Web.Macros; using Umbraco.Web.Mvc; using umbraco; using umbraco.BasePages; using umbraco.cms.businesslogic.macro; -using umbraco.cms.businesslogic.template; using umbraco.presentation.cache; using Umbraco.Core; +using Template = umbraco.cms.businesslogic.template.Template; namespace Umbraco.Web.WebServices { - - /// - /// A REST controller used for the publish dialog in order to publish bulk items at once - /// - public class BulkPublishController : UmbracoAuthorizedController - { - /// - /// Publishes an document - /// - /// - /// A Json array containing objects with the child id's of the document and it's current published status - [HttpPost] - public JsonResult PublishDocument(int documentId, bool publishChildren) - { - var doc = Services.ContentService.GetById(documentId); - var result = ((ContentService)Services.ContentService).PublishWithChildren(doc, true, UmbracoUser.Id); - - if (Services.ContentService.PublishWithChildren(doc, UmbracoUser.Id)) - { - - } - return null; - } - } - /// /// A REST controller used to save files such as templates, partial views, macro files, etc... /// diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 91d630bb9d..ed39b614b2 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -802,11 +802,11 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { var result = ((ContentService)ApplicationContext.Current.Services.ContentService).Publish(Content, false, u.Id); - _published = result; + _published = result.Success; FireAfterPublish(e); - return result; + return result.Success; } else { @@ -944,7 +944,7 @@ namespace umbraco.cms.businesslogic.web //Now we need to fire the After publish event FireAfterPublish(publishArgs); - return result; + return result.Success; } return false;