From 1ccee3f305c782379db56c2dbae21444202e611b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 14 Aug 2014 17:20:25 +0200 Subject: [PATCH 1/9] Moved all logic of PartialViewTasks and PartialViewMacroTasks to a base class as the only difference was 3 variables Added events: PartialView(Macro): Create, Save and Delete Added events: Template: Save (there's already a legacy event, this is a new one to transition to) PartialViewMacro.ascx.cs was adding ".cshtml" just so that it could strip it of again, crazy --- .../umbraco/create/PartialViewMacro.ascx.cs | 79 +++--- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../WebServices/SaveFileController.cs | 242 ++++++++++-------- .../umbraco/create/PartialViewMacrosTasks.cs | 101 +------- .../umbraco/create/PartialViewTasks.cs | 150 +---------- .../umbraco/create/PartialViewTasksBase.cs | 180 +++++++++++++ 6 files changed, 375 insertions(+), 378 deletions(-) create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs diff --git a/src/Umbraco.Web.UI/umbraco/create/PartialViewMacro.ascx.cs b/src/Umbraco.Web.UI/umbraco/create/PartialViewMacro.ascx.cs index 056f17b157..ab017f3653 100644 --- a/src/Umbraco.Web.UI/umbraco/create/PartialViewMacro.ascx.cs +++ b/src/Umbraco.Web.UI/umbraco/create/PartialViewMacro.ascx.cs @@ -9,17 +9,17 @@ using umbraco.presentation.create; namespace Umbraco.Web.UI.Umbraco.Create { - public partial class PartialViewMacro : UserControl - { - + public partial class PartialViewMacro : UserControl + { - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - DataBind(); - LoadTemplates(PartialViewTemplate); - } + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + DataBind(); + + LoadTemplates(PartialViewTemplate); + } private static void LoadTemplates(ListControl list) { @@ -45,44 +45,47 @@ namespace Umbraco.Web.UI.Umbraco.Create } } - protected void SubmitButton_Click(object sender, EventArgs e) - { - if (Page.IsValid) - { - //Seriously, need to overhaul create dialogs, this is rediculous: - // http://issues.umbraco.org/issue/U4-1373 + protected void SubmitButton_Click(object sender, EventArgs e) + { + if (Page.IsValid) + { + //Seriously, need to overhaul create dialogs, this is rediculous: + // http://issues.umbraco.org/issue/U4-1373 + + var createMacroVal = 0; + if (CreateMacroCheckBox.Checked) + createMacroVal = 1; - var createMacroVal = 0; - if (CreateMacroCheckBox.Checked) - createMacroVal = 1; - string returnUrl = dialogHandler_temp.Create(Request.GetItemAsString("nodeType"), createMacroVal, //apparently we need to pass this value to 'ParentID'... of course! :P then we'll extract it in PartialViewTasks to create it. PartialViewTemplate.SelectedValue + "|||" + FileName.Text); - - BasePage.Current.ClientTools - .ChangeContentFrameUrl(returnUrl) - .ChildNodeCreated() - .CloseModalWindow(); - } - } - protected void MacroExistsValidator_OnServerValidate(object source, ServerValidateEventArgs args) - { - if (CreateMacroCheckBox.Checked) - { + BasePage.Current.ClientTools + .ChangeContentFrameUrl(returnUrl) + .ChildNodeCreated() + .CloseModalWindow(); + } + } + + protected void MacroExistsValidator_OnServerValidate(object source, ServerValidateEventArgs args) + { + if (CreateMacroCheckBox.Checked) + { //TODO: Shouldn't this use our string functions to create the alias ? - var fileName = FileName.Text + ".cshtml"; - var name = fileName - .Substring(0, (fileName.LastIndexOf('.') + 1)).Trim('.') - .SplitPascalCasing().ToFirstUpperInvariant(); + var fileName = FileName.Text; + + var name = fileName.Contains(".") + ? fileName.Substring(0, (fileName.LastIndexOf('.') + 1)).Trim('.') + : fileName; + + name = name.SplitPascalCasing().ToFirstUpperInvariant(); var macro = ApplicationContext.Current.Services.MacroService.GetByAlias(name); if (macro != null) { args.IsValid = false; - } - } - } - } + } + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index aba7c4d109..cc45afc7e0 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -488,6 +488,7 @@ ASPXCodeBehind + ASPXCodeBehind diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 7b90da05c1..83f76d8e45 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Text; using System.Web.Mvc; +using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Web.Macros; @@ -16,55 +17,64 @@ using Template = umbraco.cms.businesslogic.template.Template; namespace Umbraco.Web.WebServices { /// - /// A REST controller used to save files such as templates, partial views, macro files, etc... - /// - /// - /// This isn't fully implemented yet but we should migrate all of the logic in the umbraco.presentation.webservices.codeEditorSave - /// over to this controller. - /// - public class SaveFileController : UmbracoAuthorizedController - { + /// A REST controller used to save files such as templates, partial views, macro files, etc... + /// + /// + /// This isn't fully implemented yet but we should migrate all of the logic in the umbraco.presentation.webservices.codeEditorSave + /// over to this controller. + /// + public class SaveFileController : UmbracoAuthorizedController + { - /// - /// Saves a partial view for a partial view macr - /// - /// - /// - /// - /// - [HttpPost] - public JsonResult SavePartialView(string filename, string oldName, string contents) - { - var folderPath = SystemDirectories.MvcViews.EnsureEndsWith('/');// +"/Partials/"; + /// + /// Saves a partial view for a partial view macr + /// + /// + /// + /// + /// + [HttpPost] + 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(...) + // validate file + IOHelper.ValidateEditPath(IOHelper.MapPath(folderPath + filename), folderPath); + // validate extension + IOHelper.ValidateFileExtension(IOHelper.MapPath(folderPath + filename), new[] { "cshtml" }.ToList()); - 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); + //TODO: Validate using the macro engine + var engine = MacroEngineFactory.GetEngine(PartialViewMacroEngine.EngineName); + //engine.Validate(...) - //Directory check.. only allow files in script dir and below to be edited - if (!savePath.StartsWith(IOHelper.MapPath(folderPath))) - { - 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)); - } + 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); - //deletes the old file - if (savePath != saveOldPath) - { - if (System.IO.File.Exists(saveOldPath)) - System.IO.File.Delete(saveOldPath); - } + //Directory check.. only allow files in script dir and below to be edited + if (!savePath.StartsWith(IOHelper.MapPath(folderPath))) + { + 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)); + } + + + 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! @@ -76,31 +86,33 @@ namespace Umbraco.Web.WebServices System.IO.File.WriteAllText(savePath, val, Encoding.UTF8); - return Success(ui.Text("speechBubbles", "partialViewSavedText"), ui.Text("speechBubbles", "partialViewSavedHeader")); - } + Saved.RaiseEvent(new SaveEventArgs(savePath, false), this); - /// - /// Saves a template - /// - /// - /// - /// - /// - /// - /// - [HttpPost] - public JsonResult SaveTemplate(string templateName, string templateAlias, string templateContents, int templateId, int masterTemplateId) - { - Template t; - bool pathChanged = false; - try - { - t = new Template(templateId) - { - Text = templateName, - Alias = templateAlias, - Design = templateContents - }; + return Success(ui.Text("speechBubbles", "partialViewSavedText"), ui.Text("speechBubbles", "partialViewSavedHeader")); + } + + /// + /// Saves a template + /// + /// + /// + /// + /// + /// + /// + [HttpPost] + public JsonResult SaveTemplate(string templateName, string templateAlias, string templateContents, int templateId, int masterTemplateId) + { + Template t; + bool pathChanged = false; + try + { + t = new Template(templateId) + { + Text = templateName, + Alias = templateAlias, + Design = templateContents + }; //check if the master page has changed if (t.MasterTemplate != masterTemplateId) @@ -108,16 +120,23 @@ namespace Umbraco.Web.WebServices pathChanged = true; t.MasterTemplate = masterTemplateId; } - } - catch (ArgumentException ex) - { - //the template does not exist - return Failed("Template does not exist", ui.Text("speechBubbles", "templateErrorHeader"), ex); - } + } + catch (ArgumentException ex) + { + //the template does not exist + return Failed("Template does not exist", ui.Text("speechBubbles", "templateErrorHeader"), ex); + } - try - { - t.Save(); + try + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(t.TemplateFilePath), this)) + { + return Failed(ui.Text("speechBubbles", "templateErrorText"), ui.Text("speechBubbles", "templateErrorHeader"), + //pass in a new exception ... this will also append the the message + new ArgumentException("Save was cancelled by an event handler: " + t.TemplateFilePath)); + } + + t.Save(); //ensure the correct path is synced as the parent might have been changed // http://issues.umbraco.org/issue/U4-2300 @@ -128,14 +147,16 @@ namespace Umbraco.Web.WebServices } var syncPath = "-1,init," + t.Path.Replace("-1,", ""); - return Success(ui.Text("speechBubbles", "templateSavedText"), ui.Text("speechBubbles", "templateSavedHeader"), - new {path = syncPath}); - } - catch (Exception ex) - { - return Failed(ui.Text("speechBubbles", "templateErrorText"), ui.Text("speechBubbles", "templateErrorHeader"), ex); - } - } + Saved.RaiseEvent(new SaveEventArgs(t.TemplateFilePath, false), this); + + return Success(ui.Text("speechBubbles", "templateSavedText"), ui.Text("speechBubbles", "templateSavedHeader"), + new { path = syncPath }); + } + catch (Exception ex) + { + return Failed(ui.Text("speechBubbles", "templateErrorText"), ui.Text("speechBubbles", "templateErrorHeader"), ex); + } + } /// /// Returns a successful message @@ -151,27 +172,36 @@ namespace Umbraco.Web.WebServices d["message"] = message; d["header"] = header; - return Json(d); - } + return Json(d); + } - /// - /// Returns a failed message - /// - /// The message to display in the speach bubble - /// The header to display in the speach bubble - /// The exception if there was one - /// - private JsonResult Failed(string message, string header, Exception exception = null) - { - if (exception != null) - LogHelper.Error("An error occurred saving a file. " + message, exception); - return Json(new - { - success = false, - header = header, - message = message + (exception == null ? "" : (exception.Message + ". Check log for details.")) - }); - } + /// + /// Returns a failed message + /// + /// The message to display in the speach bubble + /// The header to display in the speach bubble + /// The exception if there was one + /// + private JsonResult Failed(string message, string header, Exception exception = null) + { + if (exception != null) + LogHelper.Error("An error occurred saving a file. " + message, exception); + return Json(new + { + success = false, + header = header, + 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/PartialViewMacrosTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewMacrosTasks.cs index 7bde1e2b9a..36aee27fcc 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewMacrosTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewMacrosTasks.cs @@ -1,14 +1,4 @@ -using System; -using System.IO; -using System.Text.RegularExpressions; -using System.Web; -using Umbraco.Core.CodeAnnotations; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Web.Mvc; -using Umbraco.Web.UI; -using umbraco.BasePages; -using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; using umbraco.BusinessLogic; namespace umbraco @@ -17,100 +7,21 @@ namespace umbraco /// The UI 'tasks' for the create dialog and delete processes /// [UmbracoWillObsolete("http://issues.umbraco.org/issue/U4-1373", "This will one day be removed when we overhaul the create process")] - public class PartialViewMacroTasks : LegacyDialogTask + public class PartialViewMacroTasks : PartialViewTasksBase { - private const string CodeHeader = "@inherits Umbraco.Web.Macros.PartialViewMacroPage"; - private string _returnUrl = ""; - private readonly Regex _headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline | RegexOptions.Compiled); - - - protected virtual string EditViewFile + protected override string CodeHeader { - get { return "Settings/Views/EditView.aspx"; } + get { return "@inherits Umbraco.Web.Macros.PartialViewMacroPage"; } } - protected string BasePath - { - get { return SystemDirectories.MvcViews + "/" + ParentFolderName.EnsureEndsWith('/'); } - } - - protected virtual string ParentFolderName + protected override string ParentFolderName { get { return "MacroPartials"; } } - - public override bool PerformSave() - { - var pipesIndex = Alias.IndexOf("|||", System.StringComparison.Ordinal); - var template = Alias.Substring(0, pipesIndex).Trim(); - var fileName = Alias.Substring(pipesIndex + 3, Alias.Length - pipesIndex - 3) + ".cshtml"; - - var fullFilePath = IOHelper.MapPath(BasePath + fileName); - - //return the link to edit the file if it already exists - if (File.Exists(fullFilePath)) - { - _returnUrl = string.Format(EditViewFile + "?file={0}", HttpUtility.UrlEncode(ParentFolderName.EnsureEndsWith('/') + fileName)); - return true; - } - - //create the file - using (var sw = File.CreateText(fullFilePath)) - { - using (var templateFile = File.OpenText(IOHelper.MapPath(SystemDirectories.Umbraco + "/PartialViewMacros/Templates/" + template))) - { - var templateContent = templateFile.ReadToEnd().Trim(); - - //strip the @inherits if it's there - templateContent = _headerMatch.Replace(templateContent, string.Empty); - - sw.Write( - "{0}{1}{2}", - CodeHeader, - Environment.NewLine, - templateContent); - } - } - - // Create macro? - if (ParentID == 1) - { - //TODO: Shouldn't this use our string functions to create the alias ? - var name = fileName - .Substring(0, (fileName.LastIndexOf('.') + 1)).Trim('.') - .SplitPascalCasing().ToFirstUpperInvariant(); - var m = cms.businesslogic.macro.Macro.MakeNew(name); - m.ScriptingFile = BasePath + fileName; - m.Save(); - } - - _returnUrl = string.Format(EditViewFile + "?treeType={0}&file={1}", "partialViewMacros", HttpUtility.UrlEncode(ParentFolderName.EnsureEndsWith('/') + fileName)); - return true; - } - - public override string ReturnUrl - { - get { return _returnUrl; } - } - + public override string AssignedApp { get { return DefaultApps.developer.ToString(); } } - - public override bool PerformDelete() - { - var path = IOHelper.MapPath(BasePath + Alias.TrimStart('/')); - - if (File.Exists(path)) - File.Delete(path); - else if (Directory.Exists(path)) - Directory.Delete(path, true); - - LogHelper.Info(string.Format("{0} Deleted by user {1}", Alias, User.Id)); - - return true; - } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs index 5a51055e96..4fda55c201 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs @@ -1,155 +1,27 @@ -using System; -using System.IO; -using System.Text.RegularExpressions; -using System.Web; -using Umbraco.Core.CodeAnnotations; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Web.Mvc; -using Umbraco.Web.UI; -using umbraco.BasePages; -using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; using umbraco.BusinessLogic; namespace umbraco { - /// - /// The UI 'tasks' for the create dialog and delete processes - /// - [UmbracoWillObsolete("http://issues.umbraco.org/issue/U4-1373", "This will one day be removed when we overhaul the create process")] - public class PartialViewTasks : LegacyDialogTask + /// + /// The UI 'tasks' for the create dialog and delete processes + /// + [UmbracoWillObsolete("http://issues.umbraco.org/issue/U4-1373", "This will one day be removed when we overhaul the create process")] + public class PartialViewTasks : PartialViewTasksBase { - private const string CodeHeader = "@inherits Umbraco.Web.Mvc.UmbracoTemplatePage"; - - private readonly Regex _headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline | RegexOptions.Compiled); - - - protected virtual string EditViewFile + protected override string CodeHeader { - get { return "Settings/Views/EditView.aspx"; } + get { return "@inherits Umbraco.Web.Mvc.UmbracoTemplatePage"; } } - protected string BasePath - { - get { return SystemDirectories.MvcViews + "/" + ParentFolderName.EnsureEndsWith('/'); } - } - - protected virtual string ParentFolderName + protected override string ParentFolderName { get { return "Partials"; } } - - public override bool PerformSave() - { - var pipesIndex = Alias.IndexOf("|||", System.StringComparison.Ordinal); - var template = Alias.Substring(0, pipesIndex).Trim(); - var fileName = Alias.Substring(pipesIndex + 3, Alias.Length - pipesIndex - 3); - if (!fileName.ToLowerInvariant().EndsWith(".cshtml")) - { - fileName += ".cshtml"; - } - - var fullFilePath = IOHelper.MapPath(BasePath + fileName); - - //return the link to edit the file if it already exists - if (File.Exists(fullFilePath)) - { - _returnUrl = string.Format(EditViewFile + "?file={0}", HttpUtility.UrlEncode(ParentFolderName.EnsureEndsWith('/') + fileName)); - return true; - } - - //create the file - using (var sw = File.CreateText(fullFilePath)) - { - var templatePathAttempt = TryGetTemplatePath(template); - if (templatePathAttempt.Success == false) - { - throw new InvalidOperationException("Could not load template with name " + template); - } - - using (var templateFile = File.OpenText(templatePathAttempt.Result)) - { - var templateContent = templateFile.ReadToEnd().Trim(); - - //strip the @inherits if it's there - templateContent = _headerMatch.Replace(templateContent, string.Empty); - - sw.Write( - "{0}{1}{2}", - CodeHeader, - Environment.NewLine, - templateContent); - } - } - - // Create macro? - if (ParentID == 1) - { - var name = fileName - .Substring(0, (fileName.LastIndexOf('.') + 1)).Trim('.') - .SplitPascalCasing().ToFirstUpperInvariant(); - var m = cms.businesslogic.macro.Macro.MakeNew(name); - m.ScriptingFile = BasePath + fileName; - m.Save(); - } - - _returnUrl = string.Format(EditViewFile + "?file={0}", HttpUtility.UrlEncode(ParentFolderName.EnsureEndsWith('/') + fileName)); - return true; - } - - protected virtual void WriteTemplateHeader(StreamWriter sw) + + public override string AssignedApp { - //write out the template header - sw.Write("@inherits "); - sw.Write(typeof(UmbracoTemplatePage).FullName.TrimEnd("`1")); - } - - public override bool PerformDelete() - { - var path = IOHelper.MapPath(BasePath + Alias.TrimStart('/')); - - if (File.Exists(path)) - File.Delete(path); - else if (Directory.Exists(path)) - Directory.Delete(path, true); - - LogHelper.Info(string.Format("{0} Deleted by user {1}", Alias, UmbracoEnsuredPage.CurrentUser.Id)); - - return true; - } - - - private string _returnUrl = ""; - public override string ReturnUrl - { - get { return _returnUrl; } - } - - public override string AssignedApp - { get { return DefaultApps.settings.ToString(); } - } - - - /// - /// Looks first in the Partial View template folder and then in the macro partials template folder if not found - /// - /// - /// - private Attempt TryGetTemplatePath(string fileName) - { - var templatePath = IOHelper.MapPath(SystemDirectories.Umbraco + "/PartialViews/Templates/" + fileName); - - if (File.Exists(templatePath)) - { - return Attempt.Succeed(templatePath); - } - - templatePath = IOHelper.MapPath(SystemDirectories.Umbraco + "/PartialViewMacros/Templates/" + fileName); - - return File.Exists(templatePath) - ? Attempt.Succeed(templatePath) - : Attempt.Fail(); } } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs new file mode 100644 index 0000000000..30740d3f5e --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs @@ -0,0 +1,180 @@ +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; +using Umbraco.Core.CodeAnnotations; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Web.Mvc; +using Umbraco.Web.UI; +using umbraco.BasePages; +using Umbraco.Core; +using umbraco.BusinessLogic; + +namespace umbraco +{ + /// + /// The base UI 'tasks' for the create dialog and delete processes + /// + [UmbracoWillObsolete("http://issues.umbraco.org/issue/U4-1373", "This will one day be removed when we overhaul the create process")] + public abstract class PartialViewTasksBase : LegacyDialogTask + { + private readonly Regex _headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline | RegexOptions.Compiled); + + protected abstract string CodeHeader { get; } + + protected abstract string ParentFolderName { get; } + + protected virtual string EditViewFile + { + get { return "Settings/Views/EditView.aspx"; } + } + + protected string BasePath + { + get { return SystemDirectories.MvcViews + "/" + ParentFolderName.EnsureEndsWith('/'); } + } + + public override bool PerformSave() + { + var pipesIndex = Alias.IndexOf("|||", StringComparison.Ordinal); + var snippetName = Alias.Substring(0, pipesIndex).Trim(); + var fileName = Alias.Substring(pipesIndex + 3, Alias.Length - pipesIndex - 3); + if (fileName.ToLowerInvariant().EndsWith(".cshtml") == false) + { + fileName += ".cshtml"; + } + + var partialViewsFileSystem = new PhysicalFileSystem(BasePath); + var relativeFilePath = ParentFolderName.EnsureEndsWith('/') + partialViewsFileSystem.GetRelativePath(fileName); + var fullFilePath = partialViewsFileSystem.GetFullPath(fileName); + + //return the link to edit the file if it already exists + if (partialViewsFileSystem.FileExists(fullFilePath)) + { + _returnUrl = string.Format(EditViewFile + "?file={0}", HttpUtility.UrlEncode(relativeFilePath)); + return true; + } + + if (Creating.IsRaisedEventCancelled(new NewEventArgs(fullFilePath, fileName, ParentFolderName), this)) + { + return false; + } + + //create the file + var snippetPathAttempt = TryGetSnippetPath(snippetName); + if (snippetPathAttempt.Success == false) + { + throw new InvalidOperationException("Could not load template with name " + snippetName); + } + + using (var snippetFile = new StreamReader(partialViewsFileSystem.OpenFile(snippetPathAttempt.Result))) + { + var snippetContent = snippetFile.ReadToEnd().Trim(); + + //strip the @inherits if it's there + snippetContent = _headerMatch.Replace(snippetContent, string.Empty); + + var content = string.Format("{0}{1}{2}", CodeHeader, Environment.NewLine, snippetContent); + + var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)); + partialViewsFileSystem.AddFile(fullFilePath, stream); + } + + // Create macro? + if (ParentID == 1) + { + var name = fileName.Substring(0, (fileName.LastIndexOf('.') + 1)) + .Trim('.') + .SplitPascalCasing() + .ToFirstUpperInvariant(); + + var m = cms.businesslogic.macro.Macro.MakeNew(name); + + m.ScriptingFile = BasePath + fileName; + m.Save(); + } + + _returnUrl = string.Format(EditViewFile + "?file={0}", HttpUtility.UrlEncode(relativeFilePath)); + + Created.RaiseEvent(new NewEventArgs(fullFilePath, fileName, ParentFolderName), this); + + return true; + } + + protected virtual void WriteTemplateHeader(StreamWriter sw) + { + //write out the template header + sw.Write("@inherits "); + sw.Write(typeof(UmbracoTemplatePage).FullName.TrimEnd("`1")); + } + + public override bool PerformDelete() + { + var partialViewsFileSystem = new PhysicalFileSystem(BasePath); + var path = Alias.TrimStart('/'); + var fullFilePath = partialViewsFileSystem.GetFullPath(path); + + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(fullFilePath), this)) + { + return false; + } + + if (partialViewsFileSystem.FileExists(path)) + partialViewsFileSystem.DeleteFile(path); + else if (partialViewsFileSystem.DirectoryExists(path)) + partialViewsFileSystem.DeleteDirectory(path, true); + + LogHelper.Info(string.Format("{0} Deleted by user {1}", Alias, UmbracoEnsuredPage.CurrentUser.Id)); + + Deleted.RaiseEvent(new DeleteEventArgs(fullFilePath, false), this); + + return true; + } + + + private string _returnUrl = ""; + public override string ReturnUrl + { + get { return _returnUrl; } + } + + public override string AssignedApp + { + get { return string.Empty; } + } + + private Attempt TryGetSnippetPath(string fileName) + { + var partialViewsFileSystem = new PhysicalFileSystem(BasePath); + var snippetPath = IOHelper.MapPath(string.Format("{0}/PartialViewMacros/Templates/{1}", SystemDirectories.Umbraco, fileName)); + + return partialViewsFileSystem.FileExists(snippetPath) + ? Attempt.Succeed(snippetPath) + : Attempt.Fail(); + } + + /// + /// Occurs before Create + /// + internal static event TypedEventHandler> Creating; + + /// + /// Occurs after Create + /// + internal static event TypedEventHandler> Created; + + /// + /// Occurs before Delete + /// + internal static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + internal static event TypedEventHandler> Deleted; + } +} From 65a2b79189e75114b7f49877c1f59c0bc77d14fc Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 18 Aug 2014 17:47:55 +0200 Subject: [PATCH 2/9] Moves PartialView save/delete to the FileService --- src/Umbraco.Core/Models/PartialView.cs | 76 +++++++++ src/Umbraco.Core/Services/FileService.cs | 114 +++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../umbraco/create/PartialViewTasksBase.cs | 152 ++++-------------- 4 files changed, 221 insertions(+), 122 deletions(-) create mode 100644 src/Umbraco.Core/Models/PartialView.cs diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs new file mode 100644 index 0000000000..ff3dbbe367 --- /dev/null +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text.RegularExpressions; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Partial View file + /// + [Serializable] + [DataContract(IsReference = true)] + internal class PartialView : File + { + private readonly Regex _headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline | RegexOptions.Compiled); + + public PartialView(string path) + : base(path) + { + base.Path = path; + } + + /// + /// Boolean indicating whether the file could be validated + /// + /// True if file is valid, otherwise false + public override bool IsValid() + { + //Validate extension + var validExtension = IOHelper.VerifyFileExtension(Path, new List { "cshtml" }); + + return validExtension; + } + + public string FileName { get; set; } + + public string SnippetName { get; set; } + + public bool CreateMacro { get; set; } + + public string CodeHeader { get; set; } + + public string ParentFolderName { get; set; } + + public string AssignedApp { get; set; } + + public string EditViewFile { get; set; } + + public string BasePath { get; set; } + + public string ReturnUrl { get; set; } + + public bool SaveSucceeded { get; set; } + + internal Regex HeaderMatch + { + get + { + return _headerMatch; + } + } + + internal Attempt TryGetSnippetPath(string fileName) + { + var partialViewsFileSystem = new PhysicalFileSystem(BasePath); + var snippetPath = IOHelper.MapPath(string.Format("{0}/PartialViewMacros/Templates/{1}", SystemDirectories.Umbraco, fileName)); + + return partialViewsFileSystem.FileExists(snippetPath) + ? Attempt.Succeed(snippetPath) + : Attempt.Fail(); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 848097f6f0..bddbf04910 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -1,7 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Web; using Umbraco.Core.Auditing; using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories; @@ -362,6 +367,95 @@ namespace Umbraco.Core.Services return template.IsValid(); } + internal PartialView CreatePartialView(PartialView partialView) + { + var partialViewsFileSystem = new PhysicalFileSystem(partialView.BasePath); + var relativeFilePath = partialView.ParentFolderName.EnsureEndsWith('/') + partialViewsFileSystem.GetRelativePath(partialView.FileName); + + //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; + } + + if (CreatingPartialView.IsRaisedEventCancelled(new NewEventArgs(partialView, true, partialView.Alias, -1), this)) + { + partialView.SaveSucceeded = false; + return partialView; + } + + //create the file + var snippetPathAttempt = partialView.TryGetSnippetPath(partialView.SnippetName); + if (snippetPathAttempt.Success == false) + { + throw new InvalidOperationException("Could not load template with name " + partialView.SnippetName); + } + + using (var snippetFile = new StreamReader(partialViewsFileSystem.OpenFile(snippetPathAttempt.Result))) + { + var snippetContent = snippetFile.ReadToEnd().Trim(); + + //strip the @inherits if it's there + snippetContent = partialView.HeaderMatch.Replace(snippetContent, string.Empty); + + var content = string.Format("{0}{1}{2}", partialView.CodeHeader, Environment.NewLine, snippetContent); + + var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)); + partialViewsFileSystem.AddFile(partialView.Path, stream); + } + + 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; + } + + internal static void CreatePartialViewMacro(PartialView partialView) + { + var name = partialView.FileName.Substring(0, (partialView.FileName.LastIndexOf('.') + 1)) + .Trim('.') + .SplitPascalCasing() + .ToFirstUpperInvariant(); + + var macroService = new MacroService(); + var macro = new Macro(name, name) { ScriptPath = partialView.BasePath + partialView.FileName }; + macroService.Save(macro); + } + + internal bool DeletePartialView(PartialView partialView, int userId = 0) + { + var partialViewsFileSystem = new PhysicalFileSystem(partialView.BasePath); + + if (DeletingPartialView.IsRaisedEventCancelled(new DeleteEventArgs(partialView), this)) + { + return false; + } + + if (partialViewsFileSystem.FileExists(partialView.FileName)) + { + partialViewsFileSystem.DeleteFile(partialView.FileName); + LogHelper.Info(string.Format("Partial View file {0} deleted by user {1}", partialViewsFileSystem.GetFullPath(partialView.FileName), userId)); + } + // TODO: does this ever even happen? I don't think folders show up in the tree currently. + // Leaving this here as it was in the original PartialViewTasks code - SJ + else if (partialViewsFileSystem.DirectoryExists(partialView.FileName)) + { + partialViewsFileSystem.DeleteDirectory(partialView.FileName, true); + LogHelper.Info(string.Format("Partial View directory {0} deleted by user {1}", partialViewsFileSystem.GetFullPath(partialView.FileName), userId)); + } + + DeletedPartialView.RaiseEvent(new DeleteEventArgs(partialView, false), this); + + return true; + } + //TODO Method to change name and/or alias of view/masterpage template #region Event Handlers @@ -425,6 +519,26 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> SavedStylesheet; + /// + /// Occurs before Create + /// + internal static event TypedEventHandler> CreatingPartialView; + + /// + /// Occurs after Create + /// + internal static event TypedEventHandler> CreatedPartialView; + + /// + /// Occurs before Delete + /// + internal static event TypedEventHandler> DeletingPartialView; + + /// + /// Occurs after Delete + /// + internal static event TypedEventHandler> DeletedPartialView; + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 096ee04948..70fa8eddc5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -366,6 +366,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs index 30740d3f5e..7a096bf35f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs @@ -1,18 +1,11 @@ using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Web; using Umbraco.Core.CodeAnnotations; -using Umbraco.Core.Events; using Umbraco.Core.IO; -using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Web.Mvc; +using Umbraco.Core.Services; using Umbraco.Web.UI; using umbraco.BasePages; using Umbraco.Core; -using umbraco.BusinessLogic; namespace umbraco { @@ -22,12 +15,21 @@ namespace umbraco [UmbracoWillObsolete("http://issues.umbraco.org/issue/U4-1373", "This will one day be removed when we overhaul the create process")] public abstract class PartialViewTasksBase : LegacyDialogTask { - private readonly Regex _headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline | RegexOptions.Compiled); + private string _returnUrl = ""; + public override string ReturnUrl + { + get { return _returnUrl; } + } protected abstract string CodeHeader { get; } protected abstract string ParentFolderName { get; } + public override string AssignedApp + { + get { return string.Empty; } + } + protected virtual string EditViewFile { get { return "Settings/Views/EditView.aspx"; } @@ -49,67 +51,26 @@ namespace umbraco } var partialViewsFileSystem = new PhysicalFileSystem(BasePath); - var relativeFilePath = ParentFolderName.EnsureEndsWith('/') + partialViewsFileSystem.GetRelativePath(fileName); var fullFilePath = partialViewsFileSystem.GetFullPath(fileName); - //return the link to edit the file if it already exists - if (partialViewsFileSystem.FileExists(fullFilePath)) - { - _returnUrl = string.Format(EditViewFile + "?file={0}", HttpUtility.UrlEncode(relativeFilePath)); - return true; - } + var model = new PartialView(fullFilePath) + { + FileName = fileName, + SnippetName = snippetName, + CreateMacro = ParentID == 1, + CodeHeader = CodeHeader, + ParentFolderName = ParentFolderName, + AssignedApp = AssignedApp, + EditViewFile = EditViewFile, + BasePath = BasePath + }; - if (Creating.IsRaisedEventCancelled(new NewEventArgs(fullFilePath, fileName, ParentFolderName), this)) - { - return false; - } + var fileService = new FileService(); + var partialView = fileService.CreatePartialView(model); - //create the file - var snippetPathAttempt = TryGetSnippetPath(snippetName); - if (snippetPathAttempt.Success == false) - { - throw new InvalidOperationException("Could not load template with name " + snippetName); - } + _returnUrl = partialView.ReturnUrl; - using (var snippetFile = new StreamReader(partialViewsFileSystem.OpenFile(snippetPathAttempt.Result))) - { - var snippetContent = snippetFile.ReadToEnd().Trim(); - - //strip the @inherits if it's there - snippetContent = _headerMatch.Replace(snippetContent, string.Empty); - - var content = string.Format("{0}{1}{2}", CodeHeader, Environment.NewLine, snippetContent); - - var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)); - partialViewsFileSystem.AddFile(fullFilePath, stream); - } - - // Create macro? - if (ParentID == 1) - { - var name = fileName.Substring(0, (fileName.LastIndexOf('.') + 1)) - .Trim('.') - .SplitPascalCasing() - .ToFirstUpperInvariant(); - - var m = cms.businesslogic.macro.Macro.MakeNew(name); - - m.ScriptingFile = BasePath + fileName; - m.Save(); - } - - _returnUrl = string.Format(EditViewFile + "?file={0}", HttpUtility.UrlEncode(relativeFilePath)); - - Created.RaiseEvent(new NewEventArgs(fullFilePath, fileName, ParentFolderName), this); - - return true; - } - - protected virtual void WriteTemplateHeader(StreamWriter sw) - { - //write out the template header - sw.Write("@inherits "); - sw.Write(typeof(UmbracoTemplatePage).FullName.TrimEnd("`1")); + return partialView.SaveSucceeded; } public override bool PerformDelete() @@ -117,64 +78,11 @@ namespace umbraco var partialViewsFileSystem = new PhysicalFileSystem(BasePath); var path = Alias.TrimStart('/'); var fullFilePath = partialViewsFileSystem.GetFullPath(path); - - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(fullFilePath), this)) - { - return false; - } - - if (partialViewsFileSystem.FileExists(path)) - partialViewsFileSystem.DeleteFile(path); - else if (partialViewsFileSystem.DirectoryExists(path)) - partialViewsFileSystem.DeleteDirectory(path, true); - - LogHelper.Info(string.Format("{0} Deleted by user {1}", Alias, UmbracoEnsuredPage.CurrentUser.Id)); - - Deleted.RaiseEvent(new DeleteEventArgs(fullFilePath, false), this); - - return true; - } - - - private string _returnUrl = ""; - public override string ReturnUrl - { - get { return _returnUrl; } - } - - public override string AssignedApp - { - get { return string.Empty; } - } - - private Attempt TryGetSnippetPath(string fileName) - { - var partialViewsFileSystem = new PhysicalFileSystem(BasePath); - var snippetPath = IOHelper.MapPath(string.Format("{0}/PartialViewMacros/Templates/{1}", SystemDirectories.Umbraco, fileName)); - return partialViewsFileSystem.FileExists(snippetPath) - ? Attempt.Succeed(snippetPath) - : Attempt.Fail(); + var model = new PartialView(fullFilePath) { BasePath = BasePath, FileName = path }; + + var fileService = new FileService(); + return fileService.DeletePartialView(model, UmbracoEnsuredPage.CurrentUser.Id); } - - /// - /// Occurs before Create - /// - internal static event TypedEventHandler> Creating; - - /// - /// Occurs after Create - /// - internal static event TypedEventHandler> Created; - - /// - /// Occurs before Delete - /// - internal static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - internal static event TypedEventHandler> Deleted; } } From 14cbef5edf42f2b1963e4608395c3c8e6c3d210d Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 18 Aug 2014 18:02:35 +0200 Subject: [PATCH 3/9] Add some logging to the cancelled event --- src/Umbraco.Core/Services/FileService.cs | 5 +++-- .../umbraco/create/PartialViewTasksBase.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index bddbf04910..13a17c0ae8 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -382,6 +382,7 @@ namespace Umbraco.Core.Services 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; } @@ -410,8 +411,7 @@ namespace Umbraco.Core.Services 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; @@ -435,6 +435,7 @@ namespace Umbraco.Core.Services if (DeletingPartialView.IsRaisedEventCancelled(new DeleteEventArgs(partialView), this)) { + LogHelper.Info(string.Format("Deleting Partial View {0} was cancelled by an event handler.", partialViewsFileSystem.GetFullPath(partialView.FileName))); return false; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs index 7a096bf35f..0353f306d7 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs @@ -15,7 +15,7 @@ namespace umbraco [UmbracoWillObsolete("http://issues.umbraco.org/issue/U4-1373", "This will one day be removed when we overhaul the create process")] public abstract class PartialViewTasksBase : LegacyDialogTask { - private string _returnUrl = ""; + private string _returnUrl = string.Empty; public override string ReturnUrl { get { return _returnUrl; } From bba0a58bac3f4f676986afebc78909a3e5d2508c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 18 Aug 2014 18:08:01 +0200 Subject: [PATCH 4/9] AssignedApp property is of no concern to FileService --- src/Umbraco.Core/Models/PartialView.cs | 12 ++---------- .../umbraco/create/PartialViewTasksBase.cs | 1 - 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index ff3dbbe367..4ec7cf567d 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -29,9 +29,7 @@ namespace Umbraco.Core.Models public override bool IsValid() { //Validate extension - var validExtension = IOHelper.VerifyFileExtension(Path, new List { "cshtml" }); - - return validExtension; + return IOHelper.VerifyFileExtension(Path, new List { "cshtml" }); } public string FileName { get; set; } @@ -44,8 +42,6 @@ namespace Umbraco.Core.Models public string ParentFolderName { get; set; } - public string AssignedApp { get; set; } - public string EditViewFile { get; set; } public string BasePath { get; set; } @@ -56,10 +52,7 @@ namespace Umbraco.Core.Models internal Regex HeaderMatch { - get - { - return _headerMatch; - } + get { return _headerMatch; } } internal Attempt TryGetSnippetPath(string fileName) @@ -71,6 +64,5 @@ namespace Umbraco.Core.Models ? Attempt.Succeed(snippetPath) : Attempt.Fail(); } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs index 0353f306d7..0039f293c6 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs @@ -60,7 +60,6 @@ namespace umbraco CreateMacro = ParentID == 1, CodeHeader = CodeHeader, ParentFolderName = ParentFolderName, - AssignedApp = AssignedApp, EditViewFile = EditViewFile, BasePath = BasePath }; From 747ce1c01398834912281a6a7f92f360683738fc Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 19 Aug 2014 10:21:01 +0200 Subject: [PATCH 5/9] Making sure memorystream is disposed --- src/Umbraco.Core/Services/FileService.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 13a17c0ae8..40389b865e 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -403,8 +403,10 @@ namespace Umbraco.Core.Services var content = string.Format("{0}{1}{2}", partialView.CodeHeader, Environment.NewLine, snippetContent); - var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)); - partialViewsFileSystem.AddFile(partialView.Path, stream); + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + { + partialViewsFileSystem.AddFile(partialView.Path, stream); + } } if (partialView.CreateMacro) From 32057bd7eeef67003362d18bf4322449a0bcc7a9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 19 Aug 2014 10:39:52 +0200 Subject: [PATCH 6/9] Macro instead of Macr. Removed events for SaveTemplate as the old Template class already has events --- src/Umbraco.Web/WebServices/SaveFileController.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 83f76d8e45..17af82353d 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.Services; using Umbraco.Web.Macros; using Umbraco.Web.Mvc; using umbraco; @@ -27,7 +28,7 @@ namespace Umbraco.Web.WebServices { /// - /// Saves a partial view for a partial view macr + /// Saves a partial view for a partial view macro /// /// /// @@ -129,13 +130,6 @@ namespace Umbraco.Web.WebServices try { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(t.TemplateFilePath), this)) - { - return Failed(ui.Text("speechBubbles", "templateErrorText"), ui.Text("speechBubbles", "templateErrorHeader"), - //pass in a new exception ... this will also append the the message - new ArgumentException("Save was cancelled by an event handler: " + t.TemplateFilePath)); - } - t.Save(); //ensure the correct path is synced as the parent might have been changed @@ -147,8 +141,6 @@ namespace Umbraco.Web.WebServices } var syncPath = "-1,init," + t.Path.Replace("-1,", ""); - Saved.RaiseEvent(new SaveEventArgs(t.TemplateFilePath, false), this); - return Success(ui.Text("speechBubbles", "templateSavedText"), ui.Text("speechBubbles", "templateSavedHeader"), new { path = syncPath }); } From 1805857eea20a52dc77c59fc8dde612ddea6ee09 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 19 Aug 2014 12:51:07 +0200 Subject: [PATCH 7/9] Refactor PartialView.IsValid with logic found in SaveFileController Refactor FileService.CreatePartialView to use Attempt so that it gets rid of PartialView.SaveSucceeded Refactor SaveFileController to move all saving logic into the FileService Added SavingPartialView and SavedPartialView events to the FileService --- src/Umbraco.Core/Models/PartialView.cs | 14 +++- src/Umbraco.Core/Services/FileService.cs | 75 ++++++++++++++++--- .../WebServices/SaveFileController.cs | 67 ++++------------- .../umbraco/create/PartialViewTasksBase.cs | 6 +- 4 files changed, 90 insertions(+), 72 deletions(-) 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() From a9277113b187f823e0ef972dc110343a4690e580 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 19 Aug 2014 12:59:25 +0200 Subject: [PATCH 8/9] Added TODO's for when we want to make some methods public --- src/Umbraco.Core/Services/FileService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 90a7c7061c..df1a6441a8 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -368,6 +368,7 @@ namespace Umbraco.Core.Services return template.IsValid(); } + // TODO: Before making this public: How to get feedback in the UI when cancelled internal Attempt CreatePartialView(PartialView partialView) { var partialViewsFileSystem = new PhysicalFileSystem(partialView.BasePath); @@ -431,6 +432,7 @@ namespace Umbraco.Core.Services macroService.Save(macro); } + // TODO: Before making this public: How to get feedback in the UI when cancelled internal bool DeletePartialView(PartialView partialView, int userId = 0) { var partialViewsFileSystem = new PhysicalFileSystem(partialView.BasePath); From de34379ddff67d5f00affbe11d4c2f0dd8c37a10 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 20 Aug 2014 14:08:45 +0200 Subject: [PATCH 9/9] Updating PR after feedback from @sitereactor --- src/Umbraco.Core/Services/FileService.cs | 11 ++++++----- src/Umbraco.Core/Services/ServiceContext.cs | 2 +- src/Umbraco.Web/WebServices/SaveFileController.cs | 2 +- .../umbraco/create/PartialViewTasksBase.cs | 6 +++--- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index df1a6441a8..c96365f7af 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -23,19 +23,21 @@ namespace Umbraco.Core.Services private readonly RepositoryFactory _repositoryFactory; private readonly IUnitOfWorkProvider _fileUowProvider; private readonly IDatabaseUnitOfWorkProvider _dataUowProvider; + private readonly IMacroService _macroService; public FileService() : this(new RepositoryFactory()) { } public FileService(RepositoryFactory repositoryFactory) - : this(new FileUnitOfWorkProvider(), new PetaPocoUnitOfWorkProvider(), repositoryFactory) + : this(new FileUnitOfWorkProvider(), new PetaPocoUnitOfWorkProvider(), repositoryFactory, new MacroService()) { } - public FileService(IUnitOfWorkProvider fileProvider, IDatabaseUnitOfWorkProvider dataProvider, RepositoryFactory repositoryFactory) + public FileService(IUnitOfWorkProvider fileProvider, IDatabaseUnitOfWorkProvider dataProvider, RepositoryFactory repositoryFactory, IMacroService macroService) { _repositoryFactory = repositoryFactory; + _macroService = macroService; _fileUowProvider = fileProvider; _dataUowProvider = dataProvider; } @@ -420,16 +422,15 @@ namespace Umbraco.Core.Services return Attempt.Succeed(partialView); } - internal static void CreatePartialViewMacro(PartialView partialView) + internal void CreatePartialViewMacro(PartialView partialView) { var name = partialView.FileName.Substring(0, (partialView.FileName.LastIndexOf('.') + 1)) .Trim('.') .SplitPascalCasing() .ToFirstUpperInvariant(); - var macroService = new MacroService(); var macro = new Macro(name, name) { ScriptPath = partialView.BasePath + partialView.FileName }; - macroService.Save(macro); + _macroService.Save(macro); } // TODO: Before making this public: How to get feedback in the UI when cancelled diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index f358e41bf3..375e1ca1ba 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -144,7 +144,7 @@ namespace Umbraco.Core.Services _dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory.Value)); if (_fileService == null) - _fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory.Value)); + _fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory.Value, _macroService.Value)); if (_localizationService == null) _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory.Value)); diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 56f52887ce..591b89e1d4 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.WebServices Content = contents, }; - var fileService = new FileService(); + var fileService = (FileService)ApplicationContext.Current.Services.FileService; var attempt = fileService.SavePartialView(partialView); if (attempt.Success == false) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs index fdceb238d3..313cbc3efd 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs @@ -64,7 +64,7 @@ namespace umbraco BasePath = BasePath }; - var fileService = new FileService(); + var fileService = (FileService)ApplicationContext.Current.Services.FileService; var attempt = fileService.CreatePartialView(model); _returnUrl = attempt.Result.ReturnUrl; @@ -79,8 +79,8 @@ namespace umbraco var fullFilePath = partialViewsFileSystem.GetFullPath(path); var model = new PartialView(fullFilePath) { BasePath = BasePath, FileName = path }; - - var fileService = new FileService(); + + var fileService = (FileService)ApplicationContext.Current.Services.FileService; return fileService.DeletePartialView(model, UmbracoEnsuredPage.CurrentUser.Id); } }