diff --git a/src/Umbraco.Web/Actions/ActionCollection.cs b/src/Umbraco.Web/Actions/ActionCollection.cs index 70a3b67b02..64cf950c60 100644 --- a/src/Umbraco.Web/Actions/ActionCollection.cs +++ b/src/Umbraco.Web/Actions/ActionCollection.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Actions internal T GetAction() where T : IAction { - return this.OfType().SingleOrDefault(); + return this.OfType().FirstOrDefault(); } internal IEnumerable GetByLetters(IEnumerable letters) diff --git a/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs b/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs index 71b1ef48a5..6002c8d2b0 100644 --- a/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs +++ b/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using LightInject; using Umbraco.Core.Composing; @@ -14,5 +15,17 @@ namespace Umbraco.Web.Actions { } protected override ActionCollectionBuilder This => this; + + protected override IEnumerable CreateItems(params object[] args) + { + var items = base.CreateItems(args).ToList(); + //validate the items, no actions should exist that do not either expose notifications or permissions + var invalid = items.Where(x => !x.CanBePermissionAssigned && !x.ShowInNotifier).ToList(); + if (invalid.Count > 0) + { + throw new InvalidOperationException($"Invalid actions '{string.Join(", ", invalid.Select(x => x.Alias))}'. All {typeof(IAction)} implementations must be true for either {nameof(IAction.CanBePermissionAssigned)} or {nameof(IAction.ShowInNotifier)}"); + } + return items; + } } } diff --git a/src/Umbraco.Web/Actions/ActionEmptyTranscan.cs b/src/Umbraco.Web/Actions/ActionEmptyTranscan.cs deleted file mode 100644 index db70e2d2b2..0000000000 --- a/src/Umbraco.Web/Actions/ActionEmptyTranscan.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Web.UI.Pages; - - -namespace Umbraco.Web.Actions -{ - //fixme: not needed, remove this - public class ActionEmptyTranscan : IAction - { - public char Letter => 'N'; - public string Alias => "emptyRecycleBin"; - public string Category => null; - public string Icon => "trash"; - public bool ShowInNotifier => false; - public bool CanBePermissionAssigned => false; - } -} diff --git a/src/Umbraco.Web/Actions/ActionExport.cs b/src/Umbraco.Web/Actions/ActionExport.cs deleted file mode 100644 index 8843bee5ea..0000000000 --- a/src/Umbraco.Web/Actions/ActionExport.cs +++ /dev/null @@ -1,15 +0,0 @@ - - -namespace Umbraco.Web.Actions -{ - //fixme: not needed, remove this - public class ActionExport : IAction - { - public char Letter => '9'; - public string Alias => "export"; - public string Category => null; - public string Icon => "download-alt"; - public bool ShowInNotifier => false; - public bool CanBePermissionAssigned => false; - } -} diff --git a/src/Umbraco.Web/Actions/ActionImport.cs b/src/Umbraco.Web/Actions/ActionImport.cs deleted file mode 100644 index 1cd8682735..0000000000 --- a/src/Umbraco.Web/Actions/ActionImport.cs +++ /dev/null @@ -1,15 +0,0 @@ - - -namespace Umbraco.Web.Actions -{ - //fixme: not needed, remove this - public class ActionImport : IAction - { - public char Letter => '8'; - public string Alias => "importDocumentType"; - public string Category => null; - public string Icon => "page-up"; - public bool ShowInNotifier => false; - public bool CanBePermissionAssigned => false; - } -} diff --git a/src/Umbraco.Web/Actions/ActionNotify.cs b/src/Umbraco.Web/Actions/ActionNotify.cs deleted file mode 100644 index 218345a678..0000000000 --- a/src/Umbraco.Web/Actions/ActionNotify.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Web.UI.Pages; - - -namespace Umbraco.Web.Actions -{ - //fixme: not needed, remove this - public class ActionNotify : IAction - { - public char Letter => 'T'; - public string Alias => "notify"; - public string Category => null; - public string Icon => "megaphone"; - public bool ShowInNotifier => false; - public bool CanBePermissionAssigned => false; - } -} diff --git a/src/Umbraco.Web/Actions/ActionRePublish.cs b/src/Umbraco.Web/Actions/ActionRePublish.cs deleted file mode 100644 index c0af369370..0000000000 --- a/src/Umbraco.Web/Actions/ActionRePublish.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Web.UI.Pages; - - -namespace Umbraco.Web.Actions -{ - //fixme: not needed, remove this - public class ActionRePublish : IAction - { - public char Letter => 'B'; - public string Alias => "republish"; - public string Category => null; - public string Icon => "globe"; - public bool ShowInNotifier => false; - public bool CanBePermissionAssigned => false; - } -} diff --git a/src/Umbraco.Web/Actions/ActionRefresh.cs b/src/Umbraco.Web/Actions/ActionRefresh.cs deleted file mode 100644 index 38247f4e4b..0000000000 --- a/src/Umbraco.Web/Actions/ActionRefresh.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Umbraco.Web.Models.Trees; -using Umbraco.Web.UI.Pages; - - -namespace Umbraco.Web.Actions -{ - ///// - ///// This action is invoked when a node reloads its children - ///// Concerns only the tree itself and thus you should not handle - ///// this action from without umbraco. - ///// - //public class ActionRefresh : IAction - //{ - // public static string ActionAlias => "refreshNode"; - - // public char Letter => 'L'; - // public string Alias => ActionAlias; - // public string Icon => "refresh"; - // public bool ShowInNotifier => false; - // public bool CanBePermissionAssigned => false; - // public string Category => null; - //} -} diff --git a/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs b/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs index ed15b82b16..9354417155 100644 --- a/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using Umbraco.Core; +using Umbraco.Core.Services; namespace Umbraco.Web.Models.Trees { @@ -28,9 +29,17 @@ namespace Umbraco.Web.Models.Trees /// public virtual string AngularServiceMethodName { get; } = null; - [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")] - protected ActionMenuItem() - : base() + protected ActionMenuItem(string alias, string name) : base(alias, name) + { + Initialize(); + } + + protected ActionMenuItem(string alias, ILocalizedTextService textService) : base(alias, textService) + { + Initialize(); + } + + private void Initialize() { //add the current type to the metadata if (AngularServiceMethodName.IsNullOrWhiteSpace()) diff --git a/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs b/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs index 90d0e9e1cb..022056c35d 100644 --- a/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs +++ b/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs @@ -11,17 +11,16 @@ namespace Umbraco.Web.Models.Trees public override string AngularServiceName => "umbracoMenuActions"; public CreateChildEntity(string name, bool seperatorBefore = false) + : base(ActionNew.ActionAlias, name) { - Alias = ActionNew.ActionAlias; Icon = "add"; Name = name; SeperatorBefore = seperatorBefore; } public CreateChildEntity(ILocalizedTextService textService, bool seperatorBefore = false) + : base(ActionNew.ActionAlias, textService) { - Alias = ActionNew.ActionAlias; Icon = "add"; - Name = textService.Localize($"actions/{Alias}"); SeperatorBefore = seperatorBefore; } } diff --git a/src/Umbraco.Web/Models/Trees/ExportMember.cs b/src/Umbraco.Web/Models/Trees/ExportMember.cs index 5f3eaebd45..558f7c1fa1 100644 --- a/src/Umbraco.Web/Models/Trees/ExportMember.cs +++ b/src/Umbraco.Web/Models/Trees/ExportMember.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Web.Models.Trees +using Umbraco.Core.Services; + +namespace Umbraco.Web.Models.Trees { /// /// Represents the export member menu item @@ -6,5 +8,10 @@ public sealed class ExportMember : ActionMenuItem { public override string AngularServiceName => "umbracoMenuActions"; + + public ExportMember(ILocalizedTextService textService) : base("export", textService) + { + Icon = "download-alt"; + } } } diff --git a/src/Umbraco.Web/Models/Trees/MenuItem.cs b/src/Umbraco.Web/Models/Trees/MenuItem.cs index fb586e02d6..3e8a43e03e 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItem.cs @@ -31,6 +31,14 @@ namespace Umbraco.Web.Models.Trees Name = name; } + + public MenuItem(string alias, ILocalizedTextService textService) + : this() + { + Alias = alias; + Name = textService.Localize($"actions/{Alias}"); + } + /// /// Create a menu item based on an definition /// diff --git a/src/Umbraco.Web/Models/Trees/MenuItemList.cs b/src/Umbraco.Web/Models/Trees/MenuItemList.cs index 1a8a2dd88c..672750825c 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemList.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemList.cs @@ -38,28 +38,6 @@ namespace Umbraco.Web.Models.Trees return item; } - /// - /// Adds a menu item based on an - /// - /// The text to display for the menu item, will default to the IAction alias if not specified - /// - public MenuItem Add(string name) - where T : IAction - { - return Add(name, false); - } - - /// - /// Adds a menu item based on an - /// - /// The used to localize the action name based on it's alias - /// - public MenuItem Add(ILocalizedTextService textService) - where T : IAction - { - return Add(textService, false); - } - /// /// Adds a menu item with a dictionary which is merged to the AdditionalData bag /// diff --git a/src/Umbraco.Web/Models/Trees/RefreshNode.cs b/src/Umbraco.Web/Models/Trees/RefreshNode.cs index 587c51b887..2641baa34f 100644 --- a/src/Umbraco.Web/Models/Trees/RefreshNode.cs +++ b/src/Umbraco.Web/Models/Trees/RefreshNode.cs @@ -11,18 +11,16 @@ namespace Umbraco.Web.Models.Trees public override string AngularServiceName => "umbracoMenuActions"; public RefreshNode(string name, bool seperatorBefore = false) + : base("refreshNode", name) { - Alias = "refreshNode"; Icon = "refresh"; - Name = name; SeperatorBefore = seperatorBefore; } public RefreshNode(ILocalizedTextService textService, bool seperatorBefore = false) + : base("refreshNode", textService) { - Alias = "refreshNode"; Icon = "refresh"; - Name = textService.Localize($"actions/{Alias}"); SeperatorBefore = seperatorBefore; } } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index a759382854..a7598e5d3a 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -240,8 +240,12 @@ namespace Umbraco.Web.Trees AddActionNode(item, menu); AddActionNode(item, menu); AddActionNode(item, menu, true, true); - - AddActionNode(item, menu, true); + + menu.Items.Add(new MenuItem("notify", Services.TextService) + { + Icon = "megaphone", + SeperatorBefore = true + }); menu.Items.Add(new RefreshNode(Services.TextService, true)); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index c02f691f14..1398903cfc 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -349,7 +349,10 @@ namespace Umbraco.Web.Trees if (RecycleBinId.ToInvariantString() == id) { var menu = new MenuItemCollection(); - menu.Items.Add(Services.TextService.Localize("actions/emptyTrashcan")); + menu.Items.Add(new MenuItem("emptyRecycleBin", Services.TextService) + { + Icon = "trash" + }); menu.Items.Add(new RefreshNode(Services.TextService, true)); return menu; } diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 673c5f4168..32367ad02c 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -82,7 +82,11 @@ namespace Umbraco.Web.Trees // root actions menu.Items.Add(Services.TextService); - menu.Items.Add(Services.TextService, true); + menu.Items.Add(new MenuItem("importDocumentType", Services.TextService) + { + Icon = "page-up", + SeperatorBefore = true + }); menu.Items.Add(new RefreshNode(Services.TextService, true)); return menu; @@ -123,7 +127,11 @@ namespace Umbraco.Web.Trees menu.Items.Add(Services.TextService, true); } menu.Items.Add(Services.TextService); - menu.Items.Add(Services.TextService, true); + menu.Items.Add(new MenuItem("export", Services.TextService) + { + Icon = "download-alt", + SeperatorBefore = true + }); menu.Items.Add(Services.TextService, true); if (enableInheritedDocumentTypes) menu.Items.Add(new RefreshNode(Services.TextService, true)); diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index f8bcb47b56..24fc624110 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -185,12 +185,7 @@ namespace Umbraco.Web.Trees if (Security.CurrentUser.HasAccessToSensitiveData()) { - menu.Items.Add(new ExportMember - { - Name = Services.TextService.Localize("actions/export"), - Icon = "download-alt", - Alias = "export" - }); + menu.Items.Add(new ExportMember(Services.TextService)); } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f7317939dd..ff48b7ab9e 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -538,16 +538,10 @@ - - - - - -