diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 2efd21de94..f891dc83cf 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1938,6 +1938,7 @@ + Web.Template.config diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index f76066da34..333aa63b64 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -7,6 +7,9 @@ using System.Net.Http; using System.Threading.Tasks; using System.Web; using System.Web.Http; +using System.Web.Mvc; +using System.Web.Routing; +using System.Web.WebPages; using AutoMapper; using ClientDependency.Core; using Microsoft.AspNet.Identity; @@ -25,6 +28,7 @@ using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; using IUser = Umbraco.Core.Models.Membership.IUser; +using Task = System.Threading.Tasks.Task; namespace Umbraco.Web.Editors { @@ -281,22 +285,61 @@ namespace Umbraco.Web.Editors Services.UserService.Save(user); + var display = Mapper.Map(user); + + await SendEmailAsync(display, Security.CurrentUser.Name, userSave.Message); + + return display; + } + + private async Task SendEmailAsync(UserDisplay userDisplay, string from, string message) + { //now send the email - var token = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); + var token = await UserManager.GenerateEmailConfirmationTokenAsync((int)userDisplay.Id); var link = string.Format("{0}#/login/false?invite={1}{2}{3}", ApplicationContext.UmbracoApplicationUrl, - user.Id, + (int)userDisplay.Id, WebUtility.UrlEncode("|"), token.ToUrlBase64()); - await UserManager.EmailService.SendAsync(new IdentityMessage - { - Body = string.Format("You have been invited to the Umbraco Back Office!\n\n{0}\n\nClick this link to accept the invite\n\n{1}", userSave.Message, link), - Destination = userSave.Email, - Subject = "You have been invited to the Umbraco Back Office!" - }); + var virtualPath = SystemDirectories.Umbraco.EnsureEndsWith("/") + "Views/UserInvite.cshtml"; + var view = IOHelper.MapPath(virtualPath); - return Mapper.Map(user); + //This should always exist but just in case, we'll check + if (System.IO.File.Exists(view) == false) + { + await UserManager.EmailService.SendAsync(new IdentityMessage + { + Body = string.Format("You have been invited to the Umbraco Back Office!

{0}\n\nClick this link to accept the invite\n\n{1}", message, link), + Destination = userDisplay.Email, + Subject = "You have been invited to the Umbraco Back Office" + }); + } + else + { + //TODO: Inject IControllerFactory in v8 + var httpContext = TryGetHttpContext().Result; + var requestContext = new RequestContext(httpContext, new RouteData()); + var userInviteEmail = new UserInviteEmail + { + StartContentIds = userDisplay.StartContentIds, + StartMediaIds = userDisplay.StartMediaIds, + Email = userDisplay.Email, + Name = userDisplay.Name, + UserGroups = userDisplay.UserGroups, + Message = message, + InviteUrl = link, + FromName = from + }; + var viewResult = requestContext.RenderViewToString(new ViewDataDictionary(), new TempDataDictionary(), virtualPath, userInviteEmail, false); + await UserManager.EmailService.SendAsync(new IdentityMessage + { + Body = viewResult, + Destination = userDisplay.Email, + Subject = "You have been invited to the Umbraco Back Office" + }); + } + } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs index f3bcb80005..de74acffe3 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs @@ -22,5 +22,6 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "message", IsRequired = true)] [Required] public string Message { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserInviteEmail.cs b/src/Umbraco.Web/Models/ContentEditing/UserInviteEmail.cs new file mode 100644 index 0000000000..f0cc1c1879 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserInviteEmail.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Umbraco.Web.Models.ContentEditing +{ + public class UserInviteEmail + { + public string Name { get; set; } + + public string FromName { get; set; } + + public string Email { get; set; } + + public IEnumerable UserGroups { get; set; } + + public IEnumerable StartContentIds { get; set; } + + public IEnumerable StartMediaIds { get; set; } + + public string InviteUrl { get; set; } + + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/ControllerExtensions.cs b/src/Umbraco.Web/Mvc/ControllerExtensions.cs index 734e60e8f5..9e55745002 100644 --- a/src/Umbraco.Web/Mvc/ControllerExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerExtensions.cs @@ -2,6 +2,7 @@ using System.IO; using System.Threading; using System.Web.Mvc; +using System.Web.Routing; namespace Umbraco.Web.Mvc { @@ -101,16 +102,65 @@ namespace Umbraco.Web.Mvc using (var sw = new StringWriter()) { - var viewResult = !isPartial + var viewResult = isPartial == false ? ViewEngines.Engines.FindView(controller.ControllerContext, viewName, null) : ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName); - var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw); + if (viewResult.View == null) + throw new InvalidOperationException("No view could be found by name " + viewName); + var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw); viewResult.View.Render(viewContext, sw); viewResult.ViewEngine.ReleaseView(controller.ControllerContext, viewResult.View); return sw.GetStringBuilder().ToString(); } } + /// + /// Renders the partial view to string. + /// + /// The request context. + /// + /// + /// Name of the view. + /// The model. + /// true if it is a Partial view, otherwise false for a normal view + /// + internal static string RenderViewToString( + this RequestContext requestContext, + ViewDataDictionary viewData, + TempDataDictionary tempData, + string viewName, object model, bool isPartial = false) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (viewData == null) throw new ArgumentNullException("viewData"); + if (tempData == null) throw new ArgumentNullException("tempData"); + + var routeData = requestContext.RouteData; + if (routeData.Values.ContainsKey("controller") == false) + routeData.Values.Add("controller", "Fake"); + viewData.Model = model; + var controllerContext = new ControllerContext( + requestContext.HttpContext, routeData, + new FakeController + { + ViewData = viewData + }); + + using (var sw = new StringWriter()) + { + var viewResult = isPartial == false + ? ViewEngines.Engines.FindView(controllerContext, viewName, null) + : ViewEngines.Engines.FindPartialView(controllerContext, viewName); + if (viewResult.View == null) + throw new InvalidOperationException("No view could be found by name " + viewName); + var viewContext = new ViewContext(controllerContext, viewResult.View, viewData, tempData, sw); + viewResult.View.Render(viewContext, sw); + viewResult.ViewEngine.ReleaseView(controllerContext, viewResult.View); + return sw.GetStringBuilder().ToString(); + } + } + + private class FakeController : ControllerBase { protected override void ExecuteCore() { } } + /// /// Normally in MVC the way that the View object gets assigned to the result is to Execute the ViewResult, this however /// will write to the Response output stream which isn't what we want. Instead, this method will use the same logic inside diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 4ba6564409..6631c5be02 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -391,6 +391,7 @@ +