using System; using System.Collections; using System.Collections.Generic; using System.Web; using System.Reflection; using umbraco.BasePages; using umbraco.BusinessLogic.Utils; using umbraco.cms.businesslogic.web; using umbraco.cms.businesslogic.workflow; using umbraco.interfaces; using System.Text.RegularExpressions; namespace umbraco.BusinessLogic.Actions { /// /// Actions and Actionhandlers are a key concept to umbraco and a developer whom wish to apply /// businessrules whenever data is changed within umbraco, by implementing the IActionHandler /// interface it's possible to invoke methods (foreign to umbraco) - this can be used whenever /// there is a specific rule which needs to be applied to content. /// /// The Action class itself has responsibility for registering actions and actionhandlers, /// and contains methods which will be invoked whenever a change is made to ex. a document, media or member /// /// An action/actionhandler will automatically be registered, using reflection /// which is enabling thirdparty developers to extend the core functionality of /// umbraco without changing the codebase. /// public class Action { private static List _actions = new List(); private static List _actionHandlers = new List(); private static readonly List _actionJSReference = new List(); private static readonly Dictionary _actionJs = new Dictionary(); private static readonly object m_Lock = new object(); private static readonly object m_LockerReRegister = new object(); static Action() { ReRegisterActionsAndHandlers(); } /// /// This is used when an IAction or IActionHandler is installed into the system /// and needs to be loaded into memory. /// public static void ReRegisterActionsAndHandlers() { lock (m_Lock) { _actions.Clear(); _actionHandlers.Clear(); RegisterIActions(); RegisterIActionHandlers(); } } /// /// Stores all IActionHandlers that have been loaded into memory into a list /// private static void RegisterIActionHandlers() { if (_actionHandlers.Count == 0) { List foundIActionHandlers = TypeFinder.FindClassesOfType(true); foreach (Type type in foundIActionHandlers) { IActionHandler typeInstance; typeInstance = Activator.CreateInstance(type) as IActionHandler; if (typeInstance != null) _actionHandlers.Add(typeInstance); } } } /// /// Stores all IActions that have been loaded into memory into a list /// private static void RegisterIActions() { if (_actions.Count == 0) { List foundIActions = TypeFinder.FindClassesOfType(true); foreach (Type type in foundIActions) { IAction typeInstance; PropertyInfo instance = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); //if the singletone initializer is not found, try simply creating an instance of the IAction if it supports public constructors if (instance == null) typeInstance = Activator.CreateInstance(type) as IAction; else typeInstance = instance.GetValue(null, null) as IAction; if (typeInstance != null) { if (!string.IsNullOrEmpty(typeInstance.JsSource)) _actionJSReference.Add(typeInstance.JsSource); _actions.Add(typeInstance); } } } } /// /// Whenever an action is performed upon a document/media/member, this method is executed, ensuring that /// all registered handlers will have an oppotunity to handle the action. /// /// The document being operated on /// The action triggered public static void RunActionHandlers(Document d, IAction action) { foreach (IActionHandler ia in _actionHandlers) { try { foreach (IAction a in ia.ReturnActions()) { if (a.Alias == action.Alias) { // Uncommented for auto publish support // System.Web.HttpContext.Current.Trace.Write("BusinessLogic.Action.RunActionHandlers", "Running " + ia.HandlerName() + " (matching action: " + a.Alias + ")"); ia.Execute(d, action); } } } catch (Exception iaExp) { Log.Add(LogTypes.Error, User.GetUser(0), -1, string.Format("Error loading actionhandler '{0}': {1}", ia.HandlerName(), iaExp)); } } // Run notification // Find current user User u; try { u = User.GetCurrent(); } catch { u = User.GetUser(0); } if (u == null) { //GE 2012-02-29 //user will be null when using distributed calls //can't easily get the real publishing user to bubble all the way through the distributed call framework //so just check for it and set it to admin, so at least the notification gets sent u = User.GetUser(0); } Notification.GetNotifications(d, u, action); } /// /// Jacascript for the contextmenu /// Suggestion: this method should be moved to the presentation layer. /// /// /// String representation public string ReturnJavascript(string language) { return findActions(language); } /// /// Returns a list of JavaScript file paths. /// /// public static List GetJavaScriptFileReferences() { return _actionJSReference; } /// /// Javascript menuitems - tree contextmenu /// Umbraco console /// /// Suggestion: this method should be moved to the presentation layer. /// /// /// private static string findActions(string language) { if (!_actionJs.ContainsKey(language)) { string _actionJsList = ""; foreach (IAction action in _actions) { // Adding try/catch so this rutine doesn't fail if one of the actions fail // Add to language JsList try { // NH: Add support for css sprites string icon = action.Icon; if (!string.IsNullOrEmpty(icon) && icon.StartsWith(".")) icon = icon.Substring(1, icon.Length - 1); else icon = "images/" + icon; _actionJsList += string.Format(",\n\tmenuItem(\"{0}\", \"{1}\", \"{2}\", \"{3}\")", action.Letter, icon, ui.GetText("actions", action.Alias, language), action.JsFunctionName); } catch (Exception ee) { Log.Add(LogTypes.Error, -1, "Error registrering action to javascript: " + ee.ToString()); } } if (_actionJsList.Length > 0) _actionJsList = _actionJsList.Substring(2, _actionJsList.Length - 2); _actionJsList = "\nvar menuMethods = new Array(\n" + _actionJsList + "\n)\n"; _actionJs.Add(language, _actionJsList); } return _actionJs[language]; } /// /// /// /// An arraylist containing all javascript variables for the contextmenu in the tree public static ArrayList GetAll() { return new ArrayList(_actions); } /// /// This method will return a list of IAction's based on a string list. Each character in the list may represent /// an IAction. This will associate any found IActions based on the Letter property of the IAction with the character being referenced. /// /// /// returns a list of actions that have an associated letter found in the action string list public static List FromString(string actions) { List list = new List(); foreach (char c in actions.ToCharArray()) { IAction action = _actions.Find( delegate(IAction a) { return a.Letter == c; } ); if (action != null) list.Add(action); } return list; } /// /// Returns the string representation of the actions that make up the actions collection /// /// public static string ToString(List actions) { string[] strMenu = Array.ConvertAll(actions.ToArray(), delegate(IAction a) { return (a.Letter.ToString()); }); return string.Join("", strMenu); } /// /// Returns a list of IActions that are permission assignable /// /// public static List GetPermissionAssignable() { return _actions.FindAll( delegate(IAction a) { return (a.CanBePermissionAssigned); } ); } /// /// Check if the current IAction is using legacy javascript methods /// /// /// false if the Iaction is incompatible with 4.5 public static bool ValidateActionJs(IAction action) { return !action.JsFunctionName.Contains("+"); } /// /// Method to convert the old modal calls to the new ones /// /// /// public static string ConvertLegacyJs(string javascript) { MatchCollection tags = Regex.Matches(javascript, "openModal[^;]*;", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); foreach (Match tag in tags) { string[] function = tag.Value.Split(','); if (function.Length > 0) { string newFunction = "UmbClientMgr.openModalWindow" + function[0].Substring(9).Replace("parent.nodeID", "UmbClientMgr.mainTree().getActionNode().nodeId").Replace("nodeID", "UmbClientMgr.mainTree().getActionNode().nodeId").Replace("parent.returnRandom()", "'" + Guid.NewGuid().ToString() + "'"); newFunction += ", " + function[1]; newFunction += ", true"; newFunction += ", " + function[2]; newFunction += ", " + function[3]; javascript = javascript.Replace(tag.Value, newFunction); } } return javascript; } } /// /// This class is used to manipulate IActions that are implemented in a wrong way /// For instance incompatible trees with 4.0 vs 4.5 /// public class PlaceboAction : IAction { public char Letter { get; set; } public bool ShowInNotifier { get; set; } public bool CanBePermissionAssigned { get; set; } public string Icon { get; set; } public string Alias { get; set; } public string JsFunctionName { get; set; } public string JsSource { get; set; } public PlaceboAction() { } public PlaceboAction(IAction legacyAction) { Letter = legacyAction.Letter; ShowInNotifier = legacyAction.ShowInNotifier; CanBePermissionAssigned = legacyAction.CanBePermissionAssigned; Icon = legacyAction.Icon; Alias = legacyAction.Alias; JsFunctionName = legacyAction.JsFunctionName; JsSource = legacyAction.JsSource; } } }