diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index c067b2d615..ccd0821b8a 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -29,6 +29,20 @@ namespace Umbraco.Core.Services Func createSubject, Func createBody); + /// + /// Sends the notifications for the specified user regarding the specified nodes and action. + /// + /// + /// + /// + /// + /// + /// + /// + void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, + Func createSubject, + Func createBody); + /// /// Gets the notifications for the user /// diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 41e2ff698e..5d51e20008 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -56,37 +56,88 @@ namespace Umbraco.Core.Services Func createBody) { if ((entity is IContent) == false) - { throw new NotSupportedException(); - } - var content = (IContent) entity; - //we'll lazily get these if we need to send notifications - IEnumerable allVersions = null; + + var content = (IContent) entity; + + // lazily get versions - into a list to ensure we can enumerate multiple times + List allVersions = null; int totalUsers; var allUsers = _userService.GetAll(0, int.MaxValue, out totalUsers); - foreach (var u in allUsers) + foreach (var u in allUsers.Where(x => x.IsApproved)) { - if (u.IsApproved == false) continue; - var userNotifications = GetUserNotifications(u, content.Path).ToArray(); + var userNotifications = GetUserNotifications(u, content.Path); var notificationForAction = userNotifications.FirstOrDefault(x => x.Action == action); - if (notificationForAction != null) + if (notificationForAction == null) continue; + + if (allVersions == null) // lazy load + allVersions = _contentService.GetVersions(entity.Id).ToList(); + + try { - //lazy load versions if notifications are required - if (allVersions == null) - { - allVersions = _contentService.GetVersions(entity.Id); - } + SendNotification(operatingUser, u, content, allVersions, + actionName, http, createSubject, createBody); + + _logger.Debug(string.Format("Notification type: {0} sent to {1} ({2})", + action, u.Name, u.Email)); + } + catch (Exception ex) + { + _logger.Error("An error occurred sending notification", ex); + } + } + } + + /// + /// Sends the notifications for the specified user regarding the specified node and action. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Currently this will only work for Content entities! + /// + public void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, + Func createSubject, + Func createBody) + { + if ((entities is IEnumerable) == false) + throw new NotSupportedException(); + + // ensure we can enumerate multiple times + var entitiesL = entities as List ?? entities.Cast().ToList(); + + // lazily get versions - into lists to ensure we can enumerate multiple times + var allVersionsDictionary = new Dictionary>(); + + int totalUsers; + var allUsers = _userService.GetAll(0, int.MaxValue, out totalUsers); + foreach (var u in allUsers.Where(x => x.IsApproved)) + { + var userNotifications = GetUserNotifications(u).ToArray(); + + foreach (var content in entitiesL) + { + var userNotificationsByPath = FilterUserNotificationsByPath(userNotifications, content.Path); + var notificationForAction = userNotificationsByPath.FirstOrDefault(x => x.Action == action); + if (notificationForAction == null) continue; + + var allVersions = allVersionsDictionary.ContainsKey(content.Id) // lazy load + ? allVersionsDictionary[content.Id] + : allVersionsDictionary[content.Id] = _contentService.GetVersions(content.Id).ToList(); try { - SendNotification( - operatingUser, u, content, - allVersions, + SendNotification(operatingUser, u, content, allVersions, actionName, http, createSubject, createBody); - - _logger.Debug(string.Format("Notification type: {0} sent to {1} ({2})", action, u.Name, u.Email)); + _logger.Debug(string.Format("Notification type: {0} sent to {1} ({2})", + action, u.Name, u.Email)); } catch (Exception ex) { @@ -96,6 +147,7 @@ namespace Umbraco.Core.Services } } + /// /// Gets the notifications for the user /// @@ -119,10 +171,20 @@ namespace Umbraco.Core.Services /// public IEnumerable GetUserNotifications(IUser user, string path) { - var userNotifications = GetUserNotifications(user).ToArray(); - var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); - var result = userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); - return result; + var userNotifications = GetUserNotifications(user); + return FilterUserNotificationsByPath(userNotifications, path); + } + + /// + /// Filters a userNotifications collection by a path + /// + /// + /// + /// + public IEnumerable FilterUserNotificationsByPath(IEnumerable userNotifications, string path) + { + var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + return userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); } /// diff --git a/src/Umbraco.Web/NotificationServiceExtensions.cs b/src/Umbraco.Web/NotificationServiceExtensions.cs index 7beff605f9..b6182590c8 100644 --- a/src/Umbraco.Web/NotificationServiceExtensions.cs +++ b/src/Umbraco.Web/NotificationServiceExtensions.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Services; using umbraco; using umbraco.BusinessLogic.Actions; using umbraco.interfaces; +using System.Collections.Generic; namespace Umbraco.Web { @@ -27,6 +28,16 @@ namespace Umbraco.Web service.SendNotification(entity, action, UmbracoContext.Current); } + internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, ApplicationContext applicationContext) + { + if (UmbracoContext.Current == null) + { + LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + return; + } + service.SendNotification(entities, action, UmbracoContext.Current); + } + internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext) { if (umbracoContext == null) @@ -37,6 +48,16 @@ namespace Umbraco.Web service.SendNotification(entity, action, umbracoContext, umbracoContext.Application); } + internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext) + { + if (umbracoContext == null) + { + LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + return; + } + service.SendNotification(entities, action, umbracoContext, umbracoContext.Application); + } + internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) { if (umbracoContext == null) @@ -61,6 +82,30 @@ namespace Umbraco.Web service.SendNotification(user, entity, action, umbracoContext, applicationContext); } + internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) + { + if (umbracoContext == null) + { + LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + return; + } + + var user = umbracoContext.Security.CurrentUser; + + //if there is no current user, then use the admin + if (user == null) + { + LogHelper.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); + user = applicationContext.Services.UserService.GetUserById(0); + if (user == null) + { + LogHelper.Warn(typeof(NotificationServiceExtensions), "Noticiations can not be sent, no admin user with id 0 could be resolved"); + return; + } + } + service.SendNotification(user, entities, action, umbracoContext, applicationContext); + } + internal static void SendNotification(this INotificationService service, IUser sender, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) { if (sender == null) throw new ArgumentNullException("sender"); @@ -79,5 +124,24 @@ namespace Umbraco.Web ? ui.Text("notifications", "mailBody", strings, mailingUser) : ui.Text("notifications", "mailBodyHtml", strings, mailingUser)); } + + internal static void SendNotification(this INotificationService service, IUser sender, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) + { + if (sender == null) throw new ArgumentNullException("sender"); + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + + + applicationContext.Services.NotificationService.SendNotifications( + sender, + entities, + action.Letter.ToString(CultureInfo.InvariantCulture), + ui.Text("actions", action.Alias), + umbracoContext.HttpContext, + (mailingUser, strings) => ui.Text("notifications", "mailSubject", strings, mailingUser), + (mailingUser, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail + ? ui.Text("notifications", "mailBody", strings, mailingUser) + : ui.Text("notifications", "mailBodyHtml", strings, mailingUser)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/NotificationsHandler.cs b/src/Umbraco.Web/Strategies/NotificationsHandler.cs index 3eb750c1fd..a085bd463a 100644 --- a/src/Umbraco.Web/Strategies/NotificationsHandler.cs +++ b/src/Umbraco.Web/Strategies/NotificationsHandler.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Services; using umbraco; using umbraco.BusinessLogic.Actions; +using Umbraco.Core.Models; namespace Umbraco.Web.Strategies { @@ -36,6 +37,9 @@ namespace Umbraco.Web.Strategies //Send notifications for the update and created actions ContentService.Saved += (sender, args) => { + var newEntities = new List(); + var updatedEntities = new List(); + //need to determine if this is updating or if it is new foreach (var entity in args.SavedEntities) { @@ -43,16 +47,16 @@ namespace Umbraco.Web.Strategies if (dirty.WasPropertyDirty("Id")) { //it's new - applicationContext.Services.NotificationService.SendNotification( - entity, ActionNew.Instance, applicationContext); + newEntities.Add(entity); } else { //it's updating - applicationContext.Services.NotificationService.SendNotification( - entity, ActionUpdate.Instance, applicationContext); + updatedEntities.Add(entity); } } + applicationContext.Services.NotificationService.SendNotification(newEntities, ActionNew.Instance, applicationContext); + applicationContext.Services.NotificationService.SendNotification(updatedEntities, ActionUpdate.Instance, applicationContext); }; //Send notifications for the delete action diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs index e383e658aa..505d715c3e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs @@ -173,16 +173,12 @@ namespace umbraco.presentation.webservices private void SortContent(string[] ids, int parentId) { - var contentService = base.ApplicationContext.Services.ContentService; - var sortedContent = new List(); + var contentService = ApplicationContext.Services.ContentService; try { - for (var i = 0; i < ids.Length; i++) - { - var id = int.Parse(ids[i]); - var c = contentService.GetById(id); - sortedContent.Add(c); - } + var intIds = ids.Select(int.Parse).ToArray(); + var allContent = contentService.GetByIds(intIds).ToDictionary(x => x.Id, x => x); + var sortedContent = intIds.Select(x => allContent[x]); // Save content with new sort order and update db+cache accordingly var sorted = contentService.Sort(sortedContent);