diff --git a/.gitignore b/.gitignore index e2c5822dd1..027bb5d1fe 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,5 @@ src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.html src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.js src/Umbraco.Web.UI/[Cc]onfig/appSettings.config -src/Umbraco.Web.UI/[Cc]onfig/connectionStrings.config \ No newline at end of file +src/Umbraco.Web.UI/[Cc]onfig/connectionStrings.config +src/Umbraco.Web.UI/umbraco/plugins/* diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js index 4e080d02e6..f8a8f72a2e 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js @@ -204,7 +204,7 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); //get our angular navigation service var injector = getRootInjector(); - var dialogService = injector.get("dialogService"); + var dialogService = injector.get("dialogService"); var self = this; @@ -219,12 +219,14 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); //add the callback to the jquery data for the modal so we can call it on close to support the legacy way dialogs worked. dialog.element.data("modalCb", onCloseCallback); //add the close triggers - for (var i = 0; i < closeTriggers.length; i++) { - var e = dialog.find(closeTriggers[i]); - if (e.length > 0) { - e.click(function() { - self.closeModalWindow(); - }); + if (angular.isArray(closeTriggers)) { + for (var i = 0; i < closeTriggers.length; i++) { + var e = dialog.find(closeTriggers[i]); + if (e.length > 0) { + e.click(function () { + self.closeModalWindow(); + }); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js index c1970bdf92..7a45879d70 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js @@ -15,25 +15,31 @@ angular.module("umbraco.directives") //we'll try to get the jsAction from the injector var menuAction = action.metaData["jsAction"].split('.'); if (menuAction.length !== 2) { - throw "The jsAction assigned to a menu action must have two parts delimited by a '.' "; - } - var service = $injector.get(menuAction[0]); - if (!service) { - throw "The angular service " + menuAction[0] + " could not be found"; - } + //if it is not two parts long then this most likely means that it's a legacy action + var js = action.metaData["jsAction"]; + //there's not really a different way to acheive this except for eval + eval(js); - var method = service[menuAction[1]]; - - if (!method) { - throw "The method " + menuAction[1] + " on the angular service " + menuAction[0] + " could not be found"; } + else { + var service = $injector.get(menuAction[0]); + if (!service) { + throw "The angular service " + menuAction[0] + " could not be found"; + } - method.apply(this, [{ - treeNode: currentNode, - action: action, - section: currentSection - }]); + var method = service[menuAction[1]]; + + if (!method) { + throw "The method " + menuAction[1] + " on the angular service " + menuAction[0] + " could not be found"; + } + + method.apply(this, [{ + treeNode: currentNode, + action: action, + section: currentSection + }]); + } } else { //by default we launch the dialog diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 1cae76c3c9..98909ec558 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Web.Mvc; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -197,10 +198,75 @@ namespace Umbraco.Web.Editors var javascript = new StringBuilder(); javascript.AppendLine(LegacyTreeJavascript.GetLegacyTreeJavascript()); javascript.AppendLine(LegacyTreeJavascript.GetLegacyIActionJavascript()); + //add all of the menu blocks + foreach (var file in GetLegacyActionJs(LegacyJsActionType.JsBlock)) + { + javascript.AppendLine(file); + } return JavaScript(javascript.ToString()); } + /// + /// Renders out all JavaScript blocks that have bee declared in IActions + /// + private IEnumerable GetLegacyActionJs(LegacyJsActionType type) + { + var blockList = new List(); + var urlList = new List(); + foreach (var jsFile in global::umbraco.BusinessLogic.Actions.Action.GetJavaScriptFileReferences()) + { + //validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text + //block instead. + var isValid = true; + + if (Uri.IsWellFormedUriString(jsFile, UriKind.RelativeOrAbsolute)) + { + //ok it validates, but so does alert('hello'); ! so we need to do more checks + + //here are the valid chars in a url without escaping + if (Regex.IsMatch(jsFile, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]")) + isValid = false; + + //we'll have to be smarter and just check for certain js patterns now too! + var jsPatterns = new string[] {@"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"=="}; + if (jsPatterns.Any(p => Regex.IsMatch(jsFile, p))) + { + isValid = false; + } + if (isValid) + { + //it is a valid URL add to Url list + urlList.Add(jsFile); + } + } + else + { + isValid = false; + } + + if (isValid == false) + { + //it isn't a valid URL, must be a js block + blockList.Add(jsFile); + } + } + + switch (type) + { + case LegacyJsActionType.JsBlock: + return blockList; + case LegacyJsActionType.JsUrl: + return urlList; + } + + return blockList; + } + private enum LegacyJsActionType + { + JsBlock, + JsUrl + } } } diff --git a/src/Umbraco.Web/Models/Trees/MenuItemExtensions.cs b/src/Umbraco.Web/Models/Trees/MenuItemExtensions.cs index fc911ef8c3..d7784cfb47 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemExtensions.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemExtensions.cs @@ -28,6 +28,21 @@ namespace Umbraco.Web.Models.Trees /// internal const string ActionViewKey = "actionView"; + /// + /// Used to specify the js method to execute for the menu item + /// + internal const string JsActionKey = "jsAction"; + + /// + /// Adds the required meta data to the menu item so that angular knows to attempt to call the Js method. + /// + /// + /// + public static void LaunchLegacyJs(this MenuItem menuItem, string jsToExecute) + { + menuItem.SetJsAction(jsToExecute); + } + /// /// Sets the menu item to display a dialog based on an angular view path /// @@ -52,6 +67,11 @@ namespace Umbraco.Web.Models.Trees menuItem.SetActionUrl(url); } + private static void SetJsAction(this MenuItem menuItem, string jsToExecute) + { + menuItem.AdditionalData[JsActionKey] = jsToExecute; + } + /// /// Puts a dialog title into the meta data to be displayed on the dialog of the menu item (if there is one) /// instead of the menu name diff --git a/src/Umbraco.Web/Models/Trees/MenuItemList.cs b/src/Umbraco.Web/Models/Trees/MenuItemList.cs index 490e964eb8..0deedc34b1 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemList.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemList.cs @@ -169,11 +169,11 @@ namespace Umbraco.Web.Models.Trees if (attribute.MethodName.IsNullOrWhiteSpace()) { //if no method name is supplied we will assume that the menu action is the type name of the current menu class - menuItem.AdditionalData.Add("jsAction", string.Format("{0}.{1}", attribute.ServiceName, this.GetType().Name)); + menuItem.AdditionalData.Add(MenuItemExtensions.JsActionKey, string.Format("{0}.{1}", attribute.ServiceName, this.GetType().Name)); } else { - menuItem.AdditionalData.Add("jsAction", string.Format("{0}.{1}", attribute.ServiceName, attribute.MethodName)); + menuItem.AdditionalData.Add(MenuItemExtensions.JsActionKey, string.Format("{0}.{1}", attribute.ServiceName, attribute.MethodName)); } } } diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 5886dbcbd6..af55925055 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -157,9 +157,10 @@ namespace Umbraco.Web.Trees .Try(GetUrlAndTitleFromLegacyAction(currentAction, xmlTreeNode.NodeID, xmlTreeNode.NodeType, xmlTreeNode.Text, currentSection), action => menuItem.LaunchDialogUrl(action.Url, action.DialogTitle)) .OnFailure(() => GetLegacyConfirmView(currentAction, currentSection), - view => menuItem.LaunchDialogView( - view, - ui.GetText("defaultdialogs", "confirmdelete") + " '" + xmlTreeNode.Text + "' ?")); + view => menuItem.LaunchDialogView( + view, + ui.GetText("defaultdialogs", "confirmdelete") + " '" + xmlTreeNode.Text + "' ?")) + .OnFailure(() => Attempt.Succeed(true), b => menuItem.LaunchLegacyJs(menuItem.Action.JsFunctionName)); numAdded++; }