diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index 4ec7cf567d..f963ff0e1a 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -28,10 +28,18 @@ namespace Umbraco.Core.Models /// True if file is valid, otherwise false public override bool IsValid() { - //Validate extension - return IOHelper.VerifyFileExtension(Path, new List { "cshtml" }); + //TODO: Validate using the macro engine + //var engine = MacroEngineFactory.GetEngine(PartialViewMacroEngine.EngineName); + //engine.Validate(...) + + var validatePath = IOHelper.ValidateEditPath(IOHelper.MapPath(Path), BasePath); + var verifyFileExtension = IOHelper.VerifyFileExtension(Path, new List { "cshtml" }); + + return validatePath && verifyFileExtension; } + public string OldFileName { get; set; } + public string FileName { get; set; } public string SnippetName { get; set; } @@ -48,8 +56,6 @@ namespace Umbraco.Core.Models public string ReturnUrl { get; set; } - public bool SaveSucceeded { get; set; } - internal Regex HeaderMatch { get { return _headerMatch; } diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 40389b865e..90a7c7061c 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.Remoting.Messaging; using System.Text; using System.Web; using Umbraco.Core.Auditing; @@ -367,24 +368,25 @@ namespace Umbraco.Core.Services return template.IsValid(); } - internal PartialView CreatePartialView(PartialView partialView) + internal Attempt CreatePartialView(PartialView partialView) { var partialViewsFileSystem = new PhysicalFileSystem(partialView.BasePath); var relativeFilePath = partialView.ParentFolderName.EnsureEndsWith('/') + partialViewsFileSystem.GetRelativePath(partialView.FileName); + partialView.ReturnUrl = string.Format(partialView.EditViewFile + "?file={0}", HttpUtility.UrlEncode(relativeFilePath)); //return the link to edit the file if it already exists if (partialViewsFileSystem.FileExists(partialView.Path)) - { - partialView.ReturnUrl = string.Format(partialView.EditViewFile + "?file={0}", HttpUtility.UrlEncode(relativeFilePath)); - partialView.SaveSucceeded = true; - return partialView; - } + return Attempt.Succeed(partialView); if (CreatingPartialView.IsRaisedEventCancelled(new NewEventArgs(partialView, true, partialView.Alias, -1), this)) { - LogHelper.Info(string.Format("Creating Partial View {0} was cancelled by an event handler.", partialViewsFileSystem.GetFullPath(partialView.FileName))); - partialView.SaveSucceeded = false; - return partialView; + // We have nowhere to return to, clear ReturnUrl + partialView.ReturnUrl = string.Empty; + + var failureMessage = string.Format("Creating Partial View {0} was cancelled by an event handler.", partialViewsFileSystem.GetFullPath(partialView.FileName)); + LogHelper.Info(failureMessage); + + return Attempt.Fail(partialView, new ArgumentException(failureMessage)); } //create the file @@ -411,12 +413,10 @@ namespace Umbraco.Core.Services if (partialView.CreateMacro) CreatePartialViewMacro(partialView); - - partialView.ReturnUrl = string.Format(partialView.EditViewFile + "?file={0}", HttpUtility.UrlEncode(relativeFilePath)); CreatedPartialView.RaiseEvent(new NewEventArgs(partialView, false, partialView.Alias, -1), this); - return partialView; + return Attempt.Succeed(partialView); } internal static void CreatePartialViewMacro(PartialView partialView) @@ -459,6 +459,46 @@ namespace Umbraco.Core.Services return true; } + internal Attempt SavePartialView(PartialView partialView, int userId = 0) + { + if (SavingPartialView.IsRaisedEventCancelled(new SaveEventArgs(partialView, true), this)) + { + return Attempt.Fail(new ArgumentException("Save was cancelled by an event handler " + partialView.FileName)); + } + + //Directory check.. only allow files in script dir and below to be edited + if (partialView.IsValid() == false) + { + return Attempt.Fail( + new ArgumentException(string.Format("Illegal path: {0} or illegal file extension {1}", + partialView.Path, + partialView.FileName.Substring(partialView.FileName.LastIndexOf(".", StringComparison.Ordinal))))); + } + + //NOTE: I've left the below here just for informational purposes. If we save a file this way, then the UTF8 + // BOM mucks everything up, strangely, if we use WriteAllText everything is ok! + // http://issues.umbraco.org/issue/U4-2118 + //using (var sw = System.IO.File.CreateText(savePath)) + //{ + // sw.Write(val); + //} + + System.IO.File.WriteAllText(partialView.Path, partialView.Content, Encoding.UTF8); + + //deletes the old file + if (partialView.FileName != partialView.OldFileName) + { + // Create a new PartialView class so that we can set the FileName of the file that needs deleting + var deletePartial = partialView; + deletePartial.FileName = partialView.OldFileName; + DeletePartialView(deletePartial, userId); + } + + SavedPartialView.RaiseEvent(new SaveEventArgs(partialView), this); + + return Attempt.Succeed(partialView); + } + //TODO Method to change name and/or alias of view/masterpage template #region Event Handlers @@ -522,6 +562,16 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> SavedStylesheet; + /// + /// Occurs before Save + /// + internal static event TypedEventHandler> SavingPartialView; + + /// + /// Occurs after Save + /// + internal static event TypedEventHandler> SavedPartialView; + /// /// Occurs before Create /// @@ -543,5 +593,6 @@ namespace Umbraco.Core.Services internal static event TypedEventHandler> DeletedPartialView; #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 17af82353d..56f52887ce 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -5,6 +5,7 @@ using System.Web.Mvc; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Macros; using Umbraco.Web.Mvc; @@ -38,57 +39,27 @@ namespace Umbraco.Web.WebServices public JsonResult SavePartialView(string filename, string oldName, string contents) { var folderPath = SystemDirectories.MvcViews.EnsureEndsWith('/');// +"/Partials/"; - - // validate file - IOHelper.ValidateEditPath(IOHelper.MapPath(folderPath + filename), folderPath); - // validate extension - IOHelper.ValidateFileExtension(IOHelper.MapPath(folderPath + filename), new[] { "cshtml" }.ToList()); - - //TODO: Validate using the macro engine - var engine = MacroEngineFactory.GetEngine(PartialViewMacroEngine.EngineName); - //engine.Validate(...) - - var val = contents; - var saveOldPath = oldName.StartsWith("~/") ? IOHelper.MapPath(oldName) : IOHelper.MapPath(folderPath + oldName); var savePath = filename.StartsWith("~/") ? IOHelper.MapPath(filename) : IOHelper.MapPath(folderPath + filename); - //Directory check.. only allow files in script dir and below to be edited - if (!savePath.StartsWith(IOHelper.MapPath(folderPath))) + var partialView = new PartialView(savePath) + { + BasePath = folderPath, + OldFileName = oldName, + FileName = filename, + Content = contents, + }; + + var fileService = new FileService(); + var attempt = fileService.SavePartialView(partialView); + + if (attempt.Success == false) { return Failed( ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), //pass in a new exception ... this will also append the the message - new ArgumentException("Illegal path: " + savePath)); + attempt.Exception); } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(savePath), this)) - { - return Failed( - ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), - //pass in a new exception ... this will also append the the message - new ArgumentException("Save was cancelled by an event handler " + savePath)); - } - - //deletes the old file - if (savePath != saveOldPath) - { - if (System.IO.File.Exists(saveOldPath)) - System.IO.File.Delete(saveOldPath); - } - - //NOTE: I've left the below here just for informational purposes. If we save a file this way, then the UTF8 - // BOM mucks everything up, strangely, if we use WriteAllText everything is ok! - // http://issues.umbraco.org/issue/U4-2118 - //using (var sw = System.IO.File.CreateText(savePath)) - //{ - // sw.Write(val); - //} - - System.IO.File.WriteAllText(savePath, val, Encoding.UTF8); - - Saved.RaiseEvent(new SaveEventArgs(savePath, false), this); - return Success(ui.Text("speechBubbles", "partialViewSavedText"), ui.Text("speechBubbles", "partialViewSavedHeader")); } @@ -185,15 +156,5 @@ namespace Umbraco.Web.WebServices message = message + (exception == null ? "" : (exception.Message + ". Check log for details.")) }); } - - /// - /// Occurs before Create - /// - internal static event TypedEventHandler> Saving; - - /// - /// Occurs after Create - /// - internal static event TypedEventHandler> Saved; } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs index 0039f293c6..fdceb238d3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs @@ -65,11 +65,11 @@ namespace umbraco }; var fileService = new FileService(); - var partialView = fileService.CreatePartialView(model); + var attempt = fileService.CreatePartialView(model); - _returnUrl = partialView.ReturnUrl; + _returnUrl = attempt.Result.ReturnUrl; - return partialView.SaveSucceeded; + return attempt.Success; } public override bool PerformDelete()