diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs
index cfcaa921fb..bd5a3445f6 100644
--- a/src/Umbraco.Core/Events/EventExtensions.cs
+++ b/src/Umbraco.Core/Events/EventExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
namespace Umbraco.Core.Events
{
@@ -7,6 +8,32 @@ namespace Umbraco.Core.Events
///
public static class EventExtensions
{
+ ///
+ /// Raises the event and checks if a cancelation has occurred, if so it checks if any messages have been
+ /// added to the event messages collection and if not it adds a default cancelation message
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsRaisedEventCancelled(
+ this TypedEventHandler eventHandler,
+ EventMessages evtMsgs,
+ Func args,
+ TSender sender)
+ where TArgs : CancellableEventArgs
+ {
+ var evtArgs = args(evtMsgs);
+
+ if (eventHandler != null)
+ eventHandler(sender, evtArgs);
+
+ return evtArgs.Cancel;
+ }
+
///
/// Raises the event and returns a boolean value indicating if the event was cancelled
///
diff --git a/src/Umbraco.Core/Events/EventMessages.cs b/src/Umbraco.Core/Events/EventMessages.cs
index adea701d4a..2900f3d471 100644
--- a/src/Umbraco.Core/Events/EventMessages.cs
+++ b/src/Umbraco.Core/Events/EventMessages.cs
@@ -3,9 +3,9 @@ using System.Collections.Generic;
namespace Umbraco.Core.Events
{
///
- /// Default transient event messages collection
+ /// Event messages collection
///
- public sealed class EventMessages
+ public sealed class EventMessages : DisposableObject
{
private readonly List _msgs = new List();
@@ -14,9 +14,19 @@ namespace Umbraco.Core.Events
_msgs.Add(msg);
}
+ public int Count
+ {
+ get { return _msgs.Count; }
+ }
+
public IEnumerable GetAll()
{
return _msgs;
}
+
+ protected override void DisposeResources()
+ {
+ _msgs.Clear();
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/IEventMessagesFactory.cs b/src/Umbraco.Core/Events/IEventMessagesFactory.cs
index e85b853ee1..cb2391186d 100644
--- a/src/Umbraco.Core/Events/IEventMessagesFactory.cs
+++ b/src/Umbraco.Core/Events/IEventMessagesFactory.cs
@@ -2,19 +2,11 @@ using System.Collections.Generic;
namespace Umbraco.Core.Events
{
+ ///
+ /// Event messages factory
+ ///
public interface IEventMessagesFactory
{
- EventMessages CreateMessages();
- }
-
- ///
- /// A simple/default transient messages factory
- ///
- internal class TransientMessagesFactory : IEventMessagesFactory
- {
- public EventMessages CreateMessages()
- {
- return new EventMessages();
- }
+ EventMessages Get();
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs
index d3ac8b22e5..b84e28285e 100644
--- a/src/Umbraco.Core/Events/SaveEventArgs.cs
+++ b/src/Umbraco.Core/Events/SaveEventArgs.cs
@@ -4,12 +4,55 @@ namespace Umbraco.Core.Events
{
public class SaveEventArgs : CancellableObjectEventArgs>
{
- ///
- /// Constructor accepting multiple entities that are used in the saving operation
- ///
- ///
- ///
- public SaveEventArgs(IEnumerable eventObject, bool canCancel)
+ ///
+ /// Constructor accepting multiple entities that are used in the saving operation
+ ///
+ ///
+ ///
+ ///
+ public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages)
+ : base(eventObject, canCancel, eventMessages)
+ {
+ }
+
+ ///
+ /// Constructor accepting multiple entities that are used in the saving operation
+ ///
+ ///
+ ///
+ public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages)
+ : base(eventObject, eventMessages)
+ {
+ }
+
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ public SaveEventArgs(TEntity eventObject, EventMessages eventMessages)
+ : base(new List { eventObject }, eventMessages)
+ {
+ }
+
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ ///
+ public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages)
+ : base(new List { eventObject }, canCancel, eventMessages)
+ {
+ }
+
+
+ ///
+ /// Constructor accepting multiple entities that are used in the saving operation
+ ///
+ ///
+ ///
+ public SaveEventArgs(IEnumerable eventObject, bool canCancel)
: base(eventObject, canCancel)
{
}
diff --git a/src/Umbraco.Core/Events/TransientMessagesFactory.cs b/src/Umbraco.Core/Events/TransientMessagesFactory.cs
new file mode 100644
index 0000000000..5cd291a37f
--- /dev/null
+++ b/src/Umbraco.Core/Events/TransientMessagesFactory.cs
@@ -0,0 +1,13 @@
+namespace Umbraco.Core.Events
+{
+ ///
+ /// A simple/default transient messages factory
+ ///
+ internal class TransientMessagesFactory : IEventMessagesFactory
+ {
+ public EventMessages Get()
+ {
+ return new EventMessages();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index 0c861ac1ef..57073731a7 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -28,7 +28,6 @@ namespace Umbraco.Core.Services
///
public class ContentService : RepositoryService, IContentService
{
-
private readonly IPublishingStrategy _publishingStrategy;
private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer();
private readonly IDataTypeService _dataTypeService;
@@ -50,7 +49,7 @@ namespace Umbraco.Core.Services
{
if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy");
if (dataTypeService == null) throw new ArgumentNullException("dataTypeService");
- if (userService == null) throw new ArgumentNullException("userService");
+ if (userService == null) throw new ArgumentNullException("userService");
_publishingStrategy = publishingStrategy;
_dataTypeService = dataTypeService;
_userService = userService;
@@ -746,6 +745,8 @@ namespace Umbraco.Core.Services
}
}
+
+
///
/// Checks whether an item has any children
///
@@ -939,29 +940,30 @@ namespace Umbraco.Core.Services
/// The to save
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
- public void Save(IContent content, int userId = 0, bool raiseEvents = true)
+ public Attempt SaveWithStatus(IContent content, int userId = 0, bool raiseEvents = true)
{
- Save(content, true, userId, raiseEvents);
+ return Save(content, true, userId, raiseEvents);
}
///
/// Saves a collection of objects.
- ///
- ///
- /// If the collection of content contains new objects that references eachother by Id or ParentId,
- /// then use the overload Save method with a collection of Lazy .
- ///
+ ///
/// Collection of to save
/// Optional Id of the User saving the Content
- /// Optional boolean indicating whether or not to raise events.
- public void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true)
+ /// Optional boolean indicating whether or not to raise events.
+ public Attempt SaveWithStatus(IEnumerable contents, int userId = 0, bool raiseEvents = true)
{
var asArray = contents.ToArray();
if (raiseEvents)
{
- if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this))
- return;
+ if (Saving.IsRaisedEventCancelled(
+ EventMessagesFactory.Get(),
+ messages => new SaveEventArgs(asArray, messages),
+ this))
+ {
+ return Attempt.Fail(OperationStatus.Cancelled);
+ }
}
using (new WriteLock(Locker))
{
@@ -1003,9 +1005,37 @@ namespace Umbraco.Core.Services
Saved.RaiseEvent(new SaveEventArgs(asArray, false), this);
Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root);
+
+ return Attempt.Succeed(OperationStatus.Success);
}
}
+ ///
+ /// Saves a single object
+ ///
+ /// The to save
+ /// Optional Id of the User saving the Content
+ /// Optional boolean indicating whether or not to raise events.
+ public void Save(IContent content, int userId = 0, bool raiseEvents = true)
+ {
+ SaveWithStatus(content, userId, raiseEvents);
+ }
+
+ ///
+ /// Saves a collection of objects.
+ ///
+ ///
+ /// If the collection of content contains new objects that references eachother by Id or ParentId,
+ /// then use the overload Save method with a collection of Lazy .
+ ///
+ /// Collection of to save
+ /// Optional Id of the User saving the Content
+ /// Optional boolean indicating whether or not to raise events.
+ public void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true)
+ {
+ SaveWithStatus(contents, userId, raiseEvents);
+ }
+
///
/// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin.
///
@@ -1373,7 +1403,6 @@ namespace Umbraco.Core.Services
/// True if sending publication was succesfull otherwise false
public bool SendToPublication(IContent content, int userId = 0)
{
-
if (SendingToPublish.IsRaisedEventCancelled(new SendToPublishEventArgs(content), this))
return false;
@@ -1383,8 +1412,7 @@ namespace Umbraco.Core.Services
SentToPublish.RaiseEvent(new SendToPublishEventArgs(content, false), this);
Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id);
-
- //TODO: will this ever be false??
+
return true;
}
@@ -1778,7 +1806,9 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
- if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this))
+ if (Saving.IsRaisedEventCancelled(
+ EventMessagesFactory.Get(),
+ messages => new SaveEventArgs(content, messages), this))
{
return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent));
}
@@ -1867,12 +1897,17 @@ namespace Umbraco.Core.Services
/// Boolean indicating whether or not to change the Published state upon saving
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
- private void Save(IContent content, bool changeState, int userId = 0, bool raiseEvents = true)
+ private Attempt Save(IContent content, bool changeState, int userId = 0, bool raiseEvents = true)
{
if (raiseEvents)
{
- if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this))
- return;
+ if (Saving.IsRaisedEventCancelled(
+ EventMessagesFactory.Get(),
+ messages => new SaveEventArgs(content, messages),
+ this))
+ {
+ return Attempt.Fail(OperationStatus.Cancelled);
+ }
}
using (new WriteLock(Locker))
@@ -1902,6 +1937,8 @@ namespace Umbraco.Core.Services
Saved.RaiseEvent(new SaveEventArgs(content, false), this);
Audit(AuditType.Save, "Save Content performed by user", userId, content.Id);
+
+ return Attempt.Succeed(OperationStatus.Success);
}
}
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index f6c2625e23..23c67edd1c 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -195,6 +195,24 @@ namespace Umbraco.Core.Services
/// The to save
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
+ Attempt SaveWithStatus(IContent content, int userId = 0, bool raiseEvents = true);
+
+ ///
+ /// Saves a collection of objects.
+ ///
+ /// Collection of to save
+ /// Optional Id of the User saving the Content
+ /// Optional boolean indicating whether or not to raise events.
+ Attempt SaveWithStatus(IEnumerable contents, int userId = 0, bool raiseEvents = true);
+
+ ///
+ /// Saves a single object
+ ///
+ /// The to save
+ /// Optional Id of the User saving the Content
+ /// Optional boolean indicating whether or not to raise events.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Use SaveWithStatus instead, that method will provide more detailed information on the outcome")]
void Save(IContent content, int userId = 0, bool raiseEvents = true);
///
@@ -203,6 +221,8 @@ namespace Umbraco.Core.Services
/// Collection of to save
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Use SaveWithStatus instead, that method will provide more detailed information on the outcome")]
void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true);
///
@@ -347,6 +367,7 @@ namespace Umbraco.Core.Services
/// The to publish along with its children
/// Optional Id of the User issueing the publishing
/// True if publishing succeeded, otherwise False
+ [EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")]
bool PublishWithChildren(IContent content, int userId = 0);
@@ -375,6 +396,7 @@ namespace Umbraco.Core.Services
/// Optional boolean indicating whether or not to raise save events.
/// True if publishing succeeded, otherwise False
[Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true);
///
diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs
index 1de23b5b0a..ed363b6279 100644
--- a/src/Umbraco.Core/Services/IMediaService.cs
+++ b/src/Umbraco.Core/Services/IMediaService.cs
@@ -181,6 +181,24 @@ namespace Umbraco.Core.Services
/// The to save
/// Id of the User saving the Media
/// Optional boolean indicating whether or not to raise events.
+ Attempt SaveWithStatus(IMedia media, int userId = 0, bool raiseEvents = true);
+
+ ///
+ /// Saves a collection of objects
+ ///
+ /// Collection of to save
+ /// Id of the User saving the Media
+ /// Optional boolean indicating whether or not to raise events.
+ Attempt SaveWithStatus(IEnumerable medias, int userId = 0, bool raiseEvents = true);
+
+ ///
+ /// Saves a single object
+ ///
+ /// The to save
+ /// Id of the User saving the Media
+ /// Optional boolean indicating whether or not to raise events.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Use SaveWithStatus instead, that method will provide more detailed information on the outcome")]
void Save(IMedia media, int userId = 0, bool raiseEvents = true);
///
@@ -189,6 +207,8 @@ namespace Umbraco.Core.Services
/// Collection of to save
/// Id of the User saving the Media
/// Optional boolean indicating whether or not to raise events.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Use SaveWithStatus instead, that method will provide more detailed information on the outcome")]
void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true);
///
diff --git a/src/Umbraco.Core/Services/IService.cs b/src/Umbraco.Core/Services/IService.cs
index 448e09aac4..e80576c493 100644
--- a/src/Umbraco.Core/Services/IService.cs
+++ b/src/Umbraco.Core/Services/IService.cs
@@ -1,3 +1,5 @@
+using Umbraco.Core.Logging;
+
namespace Umbraco.Core.Services
{
///
@@ -5,6 +7,6 @@ namespace Umbraco.Core.Services
///
public interface IService
{
-
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs
index f73c1ce0f8..d324a51720 100644
--- a/src/Umbraco.Core/Services/MediaService.cs
+++ b/src/Umbraco.Core/Services/MediaService.cs
@@ -896,6 +896,8 @@ namespace Umbraco.Core.Services
Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id);
}
+
+
///
/// Permanently deletes versions from an object prior to a specific date.
/// This method will never delete the latest version of a content item.
@@ -955,14 +957,14 @@ namespace Umbraco.Core.Services
/// Saves a single object
///
/// The to save
- /// Id of the User saving the Content
+ /// Id of the User saving the Media
/// Optional boolean indicating whether or not to raise events.
- public void Save(IMedia media, int userId = 0, bool raiseEvents = true)
+ public Attempt SaveWithStatus(IMedia media, int userId = 0, bool raiseEvents = true)
{
if (raiseEvents)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this))
- return;
+ return Attempt.Fail(OperationStatus.Cancelled);
}
var uow = UowProvider.GetUnitOfWork();
@@ -984,22 +986,24 @@ namespace Umbraco.Core.Services
Saved.RaiseEvent(new SaveEventArgs(media, false), this);
Audit(AuditType.Save, "Save Media performed by user", userId, media.Id);
+
+ return Attempt.Succeed(OperationStatus.Success);
}
///
/// Saves a collection of objects
///
/// Collection of to save
- /// Id of the User saving the Content
+ /// Id of the User saving the Media
/// Optional boolean indicating whether or not to raise events.
- public void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true)
+ public Attempt SaveWithStatus(IEnumerable medias, int userId = 0, bool raiseEvents = true)
{
var asArray = medias.ToArray();
if (raiseEvents)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this))
- return;
+ return Attempt.Fail(OperationStatus.Cancelled);
}
var uow = UowProvider.GetUnitOfWork();
@@ -1025,6 +1029,30 @@ namespace Umbraco.Core.Services
Saved.RaiseEvent(new SaveEventArgs(asArray, false), this);
Audit(AuditType.Save, "Save Media items performed by user", userId, -1);
+
+ return Attempt.Succeed(OperationStatus.Success);
+ }
+
+ ///
+ /// Saves a single object
+ ///
+ /// The to save
+ /// Id of the User saving the Content
+ /// Optional boolean indicating whether or not to raise events.
+ public void Save(IMedia media, int userId = 0, bool raiseEvents = true)
+ {
+ SaveWithStatus(media, userId, raiseEvents);
+ }
+
+ ///
+ /// Saves a collection of objects
+ ///
+ /// Collection of to save
+ /// Id of the User saving the Content
+ /// Optional boolean indicating whether or not to raise events.
+ public void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true)
+ {
+ SaveWithStatus(medias, userId, raiseEvents);
}
///
diff --git a/src/Umbraco.Core/Services/OperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus.cs
new file mode 100644
index 0000000000..8aeaafd27a
--- /dev/null
+++ b/src/Umbraco.Core/Services/OperationStatus.cs
@@ -0,0 +1,26 @@
+namespace Umbraco.Core.Services
+{
+ ///
+ /// The status returned by many of the service methods
+ ///
+ public class OperationStatus
+ {
+ //TODO: This is a pretty simple class atm, but is 'future' proofed in case we need to add more detail here
+
+ internal static OperationStatus Cancelled
+ {
+ get { return new OperationStatus(OperationStatusType.FailedCancelledByEvent);}
+ }
+
+ internal static OperationStatus Success
+ {
+ get { return new OperationStatus(OperationStatusType.Success); }
+ }
+
+ public OperationStatus(OperationStatusType statusType)
+ {
+ StatusType = statusType;
+ }
+ public OperationStatusType StatusType { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/OperationStatusType.cs b/src/Umbraco.Core/Services/OperationStatusType.cs
new file mode 100644
index 0000000000..a54d125214
--- /dev/null
+++ b/src/Umbraco.Core/Services/OperationStatusType.cs
@@ -0,0 +1,21 @@
+namespace Umbraco.Core.Services
+{
+ ///
+ /// A status type of the result of publishing a content item
+ ///
+ ///
+ /// Anything less than 10 = Success!
+ ///
+ public enum OperationStatusType
+ {
+ ///
+ /// The saving was successful.
+ ///
+ Success = 0,
+
+ ///
+ /// The saving has been cancelled by a 3rd party add-in
+ ///
+ FailedCancelledByEvent = 14
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 1bdabacc9c..040df0b8e8 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -315,7 +315,6 @@
-
@@ -325,6 +324,7 @@
+
@@ -474,6 +474,8 @@
+
+
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
index 80b2519bfc..17317d282d 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
@@ -774,7 +774,9 @@ To manage your website, simply open the Umbraco back office and start adding con
Do not close this window during sorting]]>
- Publishing was cancelled by a 3rd party add-in
+ Cancelled
+ Operation was cancelled by a 3rd party add-in
+ Publishing was cancelled by a 3rd party add-in
Property type already exists
Property type created
DataType: %1%]]>
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 fbef663e96..87e6fabdb1 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
@@ -775,6 +775,8 @@ To manage your website, simply open the Umbraco back office and start adding con
Do not close this window during sorting]]>
+ Cancelled
+ Operation was cancelled by a 3rd party add-in
Publishing was cancelled by a 3rd party add-in
Property type already exists
Property type created
diff --git a/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs
new file mode 100644
index 0000000000..f4350bc596
--- /dev/null
+++ b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs
@@ -0,0 +1,21 @@
+using Umbraco.Web.WebApi.Filters;
+
+namespace Umbraco.Web.Editors
+{
+ ///
+ /// An abstract controller that automatically checks if any request is a non-GET and if the
+ /// resulting message is INotificationModel in which case it will append any Event Messages
+ /// currently in the request.
+ ///
+ [AppendCurrentEventMessages]
+ public abstract class BackOfficeNotificationsController : UmbracoAuthorizedJsonController
+ {
+ protected BackOfficeNotificationsController()
+ {
+ }
+
+ protected BackOfficeNotificationsController(UmbracoContext umbracoContext) : base(umbracoContext)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs
index 046a433d3a..5c8e4b9497 100644
--- a/src/Umbraco.Web/Editors/ContentController.cs
+++ b/src/Umbraco.Web/Editors/ContentController.cs
@@ -250,22 +250,24 @@ namespace Umbraco.Web.Editors
//initialize this to successful
var publishStatus = Attempt.Succeed();
+ var wasCancelled = false;
if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew)
{
//save the item
- Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id);
+ var saveResult = Services.ContentService.SaveWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id);
+ wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent;
}
else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew)
{
- Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id);
+ var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id);
+ wasCancelled = sendResult == false;
}
else
{
//publish the item and check if it worked, if not we will show a diff msg below
publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id);
}
-
//return the updated model
var display = Mapper.Map(contentItem.PersistedContent);
@@ -278,11 +280,33 @@ namespace Umbraco.Web.Editors
{
case ContentSaveAction.Save:
case ContentSaveAction.SaveNew:
- display.AddSuccessNotification(ui.Text("speechBubbles", "editContentSavedHeader"), ui.Text("speechBubbles", "editContentSavedText"));
+ if (wasCancelled == false)
+ {
+ display.AddSuccessNotification(
+ Services.TextService.Localize("speechBubbles/editContentSavedHeader"),
+ Services.TextService.Localize("speechBubbles/editContentSavedText"));
+ }
+ else
+ {
+ display.AddWarningNotification(
+ Services.TextService.Localize("speechBubbles/operationCancelledHeader"),
+ Services.TextService.Localize("speechBubbles/operationCancelledText"));
+ }
break;
case ContentSaveAction.SendPublish:
case ContentSaveAction.SendPublishNew:
- display.AddSuccessNotification(ui.Text("speechBubbles", "editContentSendToPublish"), ui.Text("speechBubbles", "editContentSendToPublishText"));
+ if (wasCancelled == false)
+ {
+ display.AddSuccessNotification(
+ Services.TextService.Localize("speechBubbles/editContentSendToPublish"),
+ Services.TextService.Localize("speechBubbles/editContentSendToPublishText"));
+ }
+ else
+ {
+ display.AddWarningNotification(
+ Services.TextService.Localize("speechBubbles/operationCancelledHeader"),
+ Services.TextService.Localize("speechBubbles/operationCancelledText"));
+ }
break;
case ContentSaveAction.Publish:
case ContentSaveAction.PublishNew:
@@ -374,23 +398,22 @@ namespace Umbraco.Web.Editors
{
case PublishStatusType.FailedPathNotPublished:
return Request.CreateValidationErrorResponse(
- ui.Text("publish", "contentPublishedFailedByParent",
- string.Format("{0} ({1})", publishResult.Result.ContentItem.Name, publishResult.Result.ContentItem.Id),
- Security.CurrentUser).Trim());
+ Services.TextService.Localize("publish/contentPublishedFailedByParent",
+ new[] {string.Format("{0} ({1})", publishResult.Result.ContentItem.Name, publishResult.Result.ContentItem.Id)}).Trim());
case PublishStatusType.FailedCancelledByEvent:
return Request.CreateValidationErrorResponse(
- ui.Text("speechBubbles", "contentPublishedFailedByEvent"));
+ Services.TextService.Localize("speechBubbles/contentPublishedFailedByEvent"));
case PublishStatusType.FailedHasExpired:
case PublishStatusType.FailedAwaitingRelease:
case PublishStatusType.FailedIsTrashed:
case PublishStatusType.FailedContentInvalid:
return Request.CreateValidationErrorResponse(
- ui.Text("publish", "contentPublishedFailedInvalid",
- new[]
- {
- string.Format("{0} ({1})", publishResult.Result.ContentItem.Name, publishResult.Result.ContentItem.Id),
- string.Join(",", publishResult.Result.InvalidProperties.Select(x => x.Alias))
- }, Security.CurrentUser));
+ Services.TextService.Localize("publish/contentPublishedFailedInvalid",
+ new[]
+ {
+ string.Format("{0} ({1})", publishResult.Result.ContentItem.Name, publishResult.Result.ContentItem.Id),
+ string.Join(",", publishResult.Result.InvalidProperties.Select(x => x.Alias))
+ }));
}
}
@@ -539,7 +562,7 @@ namespace Umbraco.Web.Editors
Services.ContentService.UnPublish(foundContent);
var content = Mapper.Map(foundContent);
- content.AddSuccessNotification(ui.Text("content", "unPublish"), ui.Text("speechBubbles", "contentUnpublished"));
+ content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished"));
return content;
}
@@ -568,7 +591,7 @@ namespace Umbraco.Web.Editors
if (toMove.ContentType.AllowedAsRoot == false)
{
throw new HttpResponseException(
- Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedAtRoot", Security.CurrentUser)));
+ Request.CreateValidationErrorResponse(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot")));
}
}
else
@@ -584,14 +607,14 @@ namespace Umbraco.Web.Editors
.Any(x => x.Value == toMove.ContentType.Id) == false)
{
throw new HttpResponseException(
- Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedByContentType", Security.CurrentUser)));
+ Request.CreateValidationErrorResponse(Services.TextService.Localize("moveOrCopy/notAllowedByContentType")));
}
// Check on paths
if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1)
{
throw new HttpResponseException(
- Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedByPath", Security.CurrentUser)));
+ Request.CreateValidationErrorResponse(Services.TextService.Localize("moveOrCopy/notAllowedByPath")));
}
}
@@ -605,30 +628,25 @@ namespace Umbraco.Web.Editors
case PublishStatusType.Success:
case PublishStatusType.SuccessAlreadyPublished:
display.AddSuccessNotification(
- ui.Text("speechBubbles", "editContentPublishedHeader", UmbracoUser),
- ui.Text("speechBubbles", "editContentPublishedText", UmbracoUser));
+ Services.TextService.Localize("speechBubbles/editContentPublishedHeader"),
+ Services.TextService.Localize("speechBubbles/editContentPublishedText"));
break;
case PublishStatusType.FailedPathNotPublished:
display.AddWarningNotification(
- ui.Text("publish"),
- ui.Text("publish", "contentPublishedFailedByParent",
- string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id),
- UmbracoUser).Trim());
+ Services.TextService.Localize("publish"),
+ Services.TextService.Localize("publish/contentPublishedFailedByParent",
+ new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim());
break;
case PublishStatusType.FailedCancelledByEvent:
display.AddWarningNotification(
- ui.Text("publish"),
- ui.Text("speechBubbles", "contentPublishedFailedByEvent"));
+ Services.TextService.Localize("publish"),
+ Services.TextService.Localize("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());
+ Services.TextService.Localize("publish"),
+ Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease",
+ new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim());
break;
case PublishStatusType.FailedHasExpired:
//TODO: We should add proper error messaging for this!
@@ -636,14 +654,13 @@ namespace Umbraco.Web.Editors
//TODO: We should add proper error messaging for this!
case PublishStatusType.FailedContentInvalid:
display.AddWarningNotification(
- ui.Text("publish"),
- ui.Text("publish", "contentPublishedFailedInvalid",
- new[]
- {
- string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id),
- string.Join(",", status.InvalidProperties.Select(x => x.Alias))
- },
- UmbracoUser).Trim());
+ Services.TextService.Localize("publish"),
+ Services.TextService.Localize("publish/contentPublishedFailedInvalid",
+ new[]
+ {
+ string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id),
+ string.Join(",", status.InvalidProperties.Select(x => x.Alias))
+ }).Trim());
break;
default:
throw new IndexOutOfRangeException();
diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs
index 5f02819f4d..d5dbdad620 100644
--- a/src/Umbraco.Web/Editors/ContentControllerBase.cs
+++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs
@@ -13,7 +13,7 @@ using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
-using Umbraco.Core.Models;
+
namespace Umbraco.Web.Editors
{
@@ -21,7 +21,7 @@ namespace Umbraco.Web.Editors
/// An abstract base controller used for media/content (and probably members) to try to reduce code replication.
///
[OutgoingDateTimeFormat]
- public abstract class ContentControllerBase : UmbracoAuthorizedJsonController
+ public abstract class ContentControllerBase : BackOfficeNotificationsController
{
///
/// Constructor
diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs
index 7f9d703763..c34170ccda 100644
--- a/src/Umbraco.Web/Editors/MediaController.cs
+++ b/src/Umbraco.Web/Editors/MediaController.cs
@@ -248,7 +248,7 @@ namespace Umbraco.Web.Editors
}
//save the item
- Services.MediaService.Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id);
+ var saveStatus = Services.MediaService.SaveWithStatus(contentItem.PersistedContent, (int)Security.CurrentUser.Id);
//return the updated model
var display = Mapper.Map(contentItem.PersistedContent);
@@ -261,7 +261,19 @@ namespace Umbraco.Web.Editors
{
case ContentSaveAction.Save:
case ContentSaveAction.SaveNew:
- display.AddSuccessNotification(ui.Text("speechBubbles", "editMediaSaved"), ui.Text("speechBubbles", "editMediaSavedText"));
+ if (saveStatus.Success)
+ {
+ display.AddSuccessNotification(
+ Services.TextService.Localize("speechBubbles/editMediaSaved"),
+ Services.TextService.Localize("speechBubbles/editMediaSavedText"));
+ }
+ else
+ {
+ display.AddWarningNotification(
+ Services.TextService.Localize("speechBubbles/operationCancelledHeader"),
+ Services.TextService.Localize("speechBubbles/operationCancelledText"));
+ }
+
break;
}
diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs
index 944b85c576..b5c3d850d2 100644
--- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs
+++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs
@@ -24,15 +24,6 @@ namespace Umbraco.Web.PublishedCache
UmbracoContext = umbracoContext;
}
- ///
- /// Informs the contextual cache that content has changed.
- ///
- /// The contextual cache may, although that is not mandatory, provide an immutable snapshot of
- /// the content over the duration of the context. If you make changes to the content and do want to have
- /// the cache update its snapshot, you have to explicitely ask it to do so by calling ContentHasChanged.
- public virtual void ContentHasChanged()
- { }
-
///
/// Gets a content identified by its unique identifier.
///
diff --git a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs
index 11842f9d45..ec3a8b2442 100644
--- a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs
+++ b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Web
_ctxAccessor = ctxAccessor;
}
- public EventMessages CreateMessages()
+ public EventMessages Get()
{
if (_ctxAccessor.Value.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name] == null)
{
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 605725598b..d92c877327 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -301,6 +301,7 @@
+
@@ -632,6 +633,7 @@
+
diff --git a/src/Umbraco.Web/UmbracoContextExtensions.cs b/src/Umbraco.Web/UmbracoContextExtensions.cs
index f75487ac66..ec85b4dad6 100644
--- a/src/Umbraco.Web/UmbracoContextExtensions.cs
+++ b/src/Umbraco.Web/UmbracoContextExtensions.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using Umbraco.Core.Events;
namespace Umbraco.Web
{
@@ -10,20 +11,18 @@ namespace Umbraco.Web
///
public static class UmbracoContextExtensions
{
+
///
- /// Informs the context that content has changed.
+ /// If there are event messages in the current request this will return them , otherwise it will return null
///
- /// The context.
- ///
- /// The contextual caches may, although that is not mandatory, provide an immutable snapshot of
- /// the content over the duration of the context. If you make changes to the content and do want to have
- /// the caches update their snapshot, you have to explicitely ask them to do so by calling ContentHasChanged.
- /// The context informs the contextual caches that content has changed.
- ///
- public static void ContentHasChanged(this UmbracoContext context)
+ ///
+ ///
+ public static EventMessages GetCurrentEventMessages(this UmbracoContext umbracoContext)
{
- context.ContentCache.ContentHasChanged();
- context.MediaCache.ContentHasChanged();
+ var msgs = umbracoContext.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name];
+ if (msgs == null) return null;
+ return (EventMessages) msgs;
}
+
}
}
diff --git a/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs
new file mode 100644
index 0000000000..fc248dc1d4
--- /dev/null
+++ b/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Net.Http;
+using System.Web.Http.Filters;
+using Umbraco.Core.Events;
+using Umbraco.Web.Models.ContentEditing;
+using Umbraco.Web.UI;
+
+namespace Umbraco.Web.WebApi.Filters
+{
+ ///
+ /// Automatically checks if any request is a non-GET and if the
+ /// resulting message is INotificationModel in which case it will append any Event Messages
+ /// currently in the request.
+ ///
+ internal sealed class AppendCurrentEventMessagesAttribute : ActionFilterAttribute
+ {
+ public override void OnActionExecuted(HttpActionExecutedContext context)
+ {
+ if (context.Response == null) return;
+ if (context.Response.IsSuccessStatusCode == false) return;
+ if (context.Request.Method == HttpMethod.Get) return;
+ if (UmbracoContext.Current == null) return;
+
+ var obj = context.Response.Content as ObjectContent;
+ if (obj == null) return;
+
+ var notifications = obj.Value as INotificationModel;
+ if (notifications == null) return;
+
+ var msgs = UmbracoContext.Current.GetCurrentEventMessages();
+ if (msgs == null) return;
+
+ foreach (var eventMessage in msgs.GetAll())
+ {
+ SpeechBubbleIcon msgType;
+ switch (eventMessage.MessageType)
+ {
+ case EventMessageType.Default:
+ msgType = SpeechBubbleIcon.Save;
+ break;
+ case EventMessageType.Info:
+ msgType = SpeechBubbleIcon.Info;
+ break;
+ case EventMessageType.Error:
+ msgType = SpeechBubbleIcon.Error;
+ break;
+ case EventMessageType.Success:
+ msgType = SpeechBubbleIcon.Success;
+ break;
+ case EventMessageType.Warning:
+ msgType = SpeechBubbleIcon.Warning;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ notifications.Notifications.Add(new Notification
+ {
+ Message = eventMessage.Message,
+ Header = eventMessage.Category,
+ NotificationType = msgType
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs
index d8e185963a..4c4d831241 100644
--- a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs
+++ b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs
@@ -2,7 +2,6 @@
using System.Linq;
using System.Net.Http.Formatting;
using System.Web.Http.Controllers;
-using System.Web.Http.Filters;
using Umbraco.Core;
namespace Umbraco.Web.WebApi.Filters
@@ -10,7 +9,7 @@ namespace Umbraco.Web.WebApi.Filters
///
/// Sets the json outgoing/serialized datetime format
///
- internal sealed class OutgoingDateTimeFormatAttribute : Attribute, IControllerConfiguration
+ internal sealed class OutgoingDateTimeFormatAttribute : Attribute, IControllerConfiguration
{
private readonly string _format = "yyyy-MM-dd HH:mm:ss";
@@ -27,8 +26,9 @@ namespace Umbraco.Web.WebApi.Filters
///
/// Will use the standard ISO format
///
- public OutgoingDateTimeFormatAttribute(){
-
+ public OutgoingDateTimeFormatAttribute()
+ {
+
}
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)