-
-
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js
index 961470b03a..a11aa8bff8 100644
--- a/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js
@@ -113,11 +113,9 @@
notificationsService.success(value);
});
- // emit event when language is created
- if($routeParams.create) {
- var args = { language: lang };
- eventsService.emit("editors.languages.languageCreated", args);
- }
+ // emit event when language is created or updated/saved
+ var args = { language: lang, isNew: $routeParams.create ? true : false };
+ eventsService.emit("editors.languages.languageSaved", args);
back();
@@ -129,7 +127,7 @@
});
}
-
+
}
function back() {
@@ -145,7 +143,7 @@
}
function toggleDefault() {
-
+
// it shouldn't be possible to uncheck the default language
if(vm.initIsDefault) {
return;
diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html
index dfec56fbc0..c55ce83417 100644
--- a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html
+++ b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html
@@ -41,7 +41,7 @@
- {{ language.name }}
+ {{ language.name }}
{{ language.culture }}
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdown/dropdown.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdown/dropdown.controller.js
deleted file mode 100644
index bcbd13f860..0000000000
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdown/dropdown.controller.js
+++ /dev/null
@@ -1,70 +0,0 @@
-angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownController",
- function($scope) {
-
- //setup the default config
- var config = {
- items: [],
- multiple: false
- };
-
- //map the user config
- angular.extend(config, $scope.model.config);
-
- //map back to the model
- $scope.model.config = config;
-
- function convertArrayToDictionaryArray(model){
- //now we need to format the items in the dictionary because we always want to have an array
- var newItems = [];
- for (var i = 0; i < model.length; i++) {
- newItems.push({ id: model[i], sortOrder: 0, value: model[i] });
- }
-
- return newItems;
- }
-
-
- function convertObjectToDictionaryArray(model){
- //now we need to format the items in the dictionary because we always want to have an array
- var newItems = [];
- var vals = _.values($scope.model.config.items);
- var keys = _.keys($scope.model.config.items);
-
- for (var i = 0; i < vals.length; i++) {
- var label = vals[i].value ? vals[i].value : vals[i];
- newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: label });
- }
-
- return newItems;
- }
-
- if (angular.isArray($scope.model.config.items)) {
- //PP: I dont think this will happen, but we have tests that expect it to happen..
- //if array is simple values, convert to array of objects
- if(!angular.isObject($scope.model.config.items[0])){
- $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
- }
- }
- else if (angular.isObject($scope.model.config.items)) {
- $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
- }
- else {
- throw "The items property must be either an array or a dictionary";
- }
-
-
- //sort the values
- $scope.model.config.items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
-
- //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
- // to "" gets selected by default
- if ($scope.model.value === null || $scope.model.value === undefined) {
- if ($scope.model.config.multiple) {
- $scope.model.value = [];
- }
- else {
- $scope.model.value = "";
- }
- }
-
- });
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdown/dropdown.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdown/dropdown.html
deleted file mode 100644
index 663373c8eb..0000000000
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdown/dropdown.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js
index c7a472d80e..f8e02a240a 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js
@@ -12,7 +12,10 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo
//map back to the model
$scope.model.config = config;
-
+
+ //ensure this is a bool, old data could store zeros/ones or string versions
+ $scope.model.config.multiple = Object.toBoolean($scope.model.config.multiple);
+
function convertArrayToDictionaryArray(model){
//now we need to format the items in the dictionary because we always want to have an array
var newItems = [];
@@ -74,7 +77,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo
// if we run in single mode we'll store the value in a local variable
// so we can pass an array as the model as our PropertyValueEditor expects that
$scope.model.singleDropdownValue = "";
- if ($scope.model.config.multiple === "0") {
+ if (!Object.toBoolean($scope.model.config.multiple)) {
$scope.model.singleDropdownValue = Array.isArray($scope.model.value) ? $scope.model.value[0] : $scope.model.value;
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html
index 5edbab4f30..3239e64acc 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html
@@ -5,15 +5,15 @@
ng-switch-default
ng-change="updateSingleDropdownValue()"
ng-model="model.singleDropdownValue"
- ng-options="item.id as item.value for item in model.config.items">
+ ng-options="item.value as item.value for item in model.config.items">
+ ng-options="item.value as item.value for item in model.config.items">
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index 3dc5cd053a..d2867acfc9 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -324,8 +324,6 @@
-
- ASPXCodeBehind
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml
index 60800cc3b1..edf973476b 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml
@@ -143,14 +143,14 @@
Content deletedContent unpublishedContent saved and Published
- Content cultures %0% saved and published
+ Content saved and published for languages: %0% Content saved
- Content cultures %0% saved
+ Content saved for languages: %0%Content movedContent copiedContent rolled backContent sent for publishing
- Content cultures %0% sent for publishing
+ Content sent for publishing for languages: %0%Sort child items performed by userCopyPublish
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml
index 9fd609fa95..0a250cc980 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml
@@ -148,14 +148,14 @@
Content deletedContent unpublishedContent saved and Published
- Content cultures %0% saved and published
+ Content saved and published for languages: %0% Content saved
- Content cultures %0% saved
+ Content saved for languages: %0%Content movedContent copiedContent rolled backContent sent for publishing
- Content cultures %0% sent for publishing
+ Content sent for publishing for languages: %0%Sort child items performed by userCopyPublish
diff --git a/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js b/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js
deleted file mode 100644
index 30d6611ba9..0000000000
--- a/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js
+++ /dev/null
@@ -1,21 +0,0 @@
-function actionDeleteRelationType(relationTypeId, relationTypeName) {
-
- if (confirm('Are you sure you want to delete "' + relationTypeName + '"?')) {
- $.ajax({
- type: "POST",
- url: "developer/RelationTypes/RelationTypesWebService.asmx/DeleteRelationType",
- data: "{ 'relationTypeId' : '" + relationTypeId + "' }",
- contentType: "application/json; charset=utf-8",
- dataType: "json",
- success: function (data) {
- UmbClientMgr.mainTree().refreshTree('relationTypes');
- UmbClientMgr.appActions().openDashboard('developer');
- },
- error: function (data) { }
- });
-
- }
-
-}
-
-
diff --git a/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.js b/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.js
deleted file mode 100644
index 322e4b9ace..0000000000
--- a/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.js
+++ /dev/null
@@ -1,3 +0,0 @@
-function actionNewRelationType() {
- UmbClientMgr.openModalWindow('developer/RelationTypes/NewRelationType.aspx', 'Create New RelationType', true, 400, 300, 0, 0);
-}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/Umbraco/dialogs/ChangeDocType.aspx.cs
index 2297ea1aa7..8111410edd 100644
--- a/src/Umbraco.Web.UI/Umbraco/dialogs/ChangeDocType.aspx.cs
+++ b/src/Umbraco.Web.UI/Umbraco/dialogs/ChangeDocType.aspx.cs
@@ -113,7 +113,8 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs
private IEnumerable RemoveInvalidByChildrenDocumentTypesFromAlternatives(IEnumerable documentTypes)
{
- var docTypeIdsOfChildren = _content.Children(Services.ContentService)
+ //fixme Should do proper paging here ... when this is refactored we will
+ var docTypeIdsOfChildren = Services.ContentService.GetPagedChildren(_content.Id, 0, int.MaxValue, out var total)
.Select(x => x.ContentType.Id)
.Distinct()
.ToList();
diff --git a/src/Umbraco.Web/Actions/ActionAssignDomain.cs b/src/Umbraco.Web/Actions/ActionAssignDomain.cs
new file mode 100644
index 0000000000..7dc3668e5d
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionAssignDomain.cs
@@ -0,0 +1,21 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when a domain is being assigned to a document
+ ///
+ public class ActionAssignDomain : IAction
+ {
+ public const char ActionLetter = 'I';
+
+ public char Letter => ActionLetter;
+ public string Alias => "assignDomain";
+ public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory;
+ public string Icon => "home";
+ public bool ShowInNotifier => false;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionBrowse.cs b/src/Umbraco.Web/Actions/ActionBrowse.cs
new file mode 100644
index 0000000000..64882c142a
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionBrowse.cs
@@ -0,0 +1,27 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is used as a security constraint that grants a user the ability to view nodes in a tree
+ /// that has permissions applied to it.
+ ///
+ ///
+ /// This action should not be invoked. It is used as the minimum required permission to view nodes in the content tree. By
+ /// granting a user this permission, the user is able to see the node in the tree but not edit the document. This may be used by other trees
+ /// that support permissions in the future.
+ ///
+ public class ActionBrowse : IAction
+ {
+ public const char ActionLetter = 'F';
+
+ public char Letter => ActionLetter;
+ public bool ShowInNotifier => false;
+ public bool CanBePermissionAssigned => true;
+ public string Icon => "";
+ public string Alias => "browse";
+ public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionChangeDocType.cs b/src/Umbraco.Web/Actions/ActionChangeDocType.cs
new file mode 100644
index 0000000000..73772699d0
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionChangeDocType.cs
@@ -0,0 +1,20 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when the document type of a piece of content is changed
+ ///
+ public class ActionChangeDocType : IAction
+ {
+ public char Letter => '7';
+ public string Alias => "changeDocType";
+ public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory;
+ public string Icon => "axis-rotation-2";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs b/src/Umbraco.Web/Actions/ActionCollection.cs
similarity index 57%
rename from src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs
rename to src/Umbraco.Web/Actions/ActionCollection.cs
index 849fb3b619..64cf950c60 100644
--- a/src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs
+++ b/src/Umbraco.Web/Actions/ActionCollection.cs
@@ -3,8 +3,10 @@ using System.Globalization;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Composing;
+using Umbraco.Core.Models.Membership;
-namespace Umbraco.Web._Legacy.Actions
+
+namespace Umbraco.Web.Actions
{
public class ActionCollection : BuilderCollectionBase
{
@@ -15,7 +17,7 @@ namespace Umbraco.Web._Legacy.Actions
internal T GetAction()
where T : IAction
{
- return this.OfType().SingleOrDefault();
+ return this.OfType().FirstOrDefault();
}
internal IEnumerable GetByLetters(IEnumerable letters)
@@ -25,5 +27,15 @@ namespace Umbraco.Web._Legacy.Actions
.WhereNotNull()
.ToArray();
}
+
+ internal IReadOnlyList FromEntityPermission(EntityPermission entityPermission)
+ {
+ return entityPermission.AssignedPermissions
+ .Where(x => x.Length == 1)
+ .Select(x => x.ToCharArray()[0])
+ .SelectMany(c => this.Where(x => x.Letter == c))
+ .Where(action => action != null)
+ .ToList();
+ }
}
}
diff --git a/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs b/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs
new file mode 100644
index 0000000000..6002c8d2b0
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using LightInject;
+using Umbraco.Core.Composing;
+
+
+namespace Umbraco.Web.Actions
+{
+ internal class ActionCollectionBuilder : LazyCollectionBuilderBase
+ {
+ public ActionCollectionBuilder(IServiceContainer container)
+ : base(container)
+ { }
+
+ 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/ActionCopy.cs b/src/Umbraco.Web/Actions/ActionCopy.cs
new file mode 100644
index 0000000000..3e4b2ddc31
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionCopy.cs
@@ -0,0 +1,20 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when copying a document, media, member
+ ///
+ public class ActionCopy : IAction
+ {
+ public char Letter => 'O';
+ public string Alias => "copy";
+ public string Category => Constants.Conventions.PermissionCategories.StructureCategory;
+ public string Icon => "documents";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionCreateBlueprintFromContent.cs b/src/Umbraco.Web/Actions/ActionCreateBlueprintFromContent.cs
new file mode 100644
index 0000000000..0a46393a81
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionCreateBlueprintFromContent.cs
@@ -0,0 +1,16 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+
+
+namespace Umbraco.Web.Actions
+{
+ public class ActionCreateBlueprintFromContent : IAction
+ {
+ public char Letter => 'ï';
+ public bool ShowInNotifier => false;
+ public bool CanBePermissionAssigned => true;
+ public string Icon => "blueprint";
+ public string Alias => "createblueprint";
+ public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionDelete.cs b/src/Umbraco.Web/Actions/ActionDelete.cs
new file mode 100644
index 0000000000..0cf2e60c5a
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionDelete.cs
@@ -0,0 +1,23 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when a document, media, member is deleted
+ ///
+ public class ActionDelete : IAction
+ {
+ public const string ActionAlias = "delete";
+ public const char ActionLetter = 'D';
+
+ public char Letter => ActionLetter;
+ public string Alias => ActionAlias;
+ public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
+ public string Icon => "delete";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionMove.cs b/src/Umbraco.Web/Actions/ActionMove.cs
new file mode 100644
index 0000000000..7ae8474965
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionMove.cs
@@ -0,0 +1,22 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked upon creation of a document, media, member
+ ///
+ public class ActionMove : IAction
+ {
+ public const char ActionLetter = 'M';
+
+ public char Letter => ActionLetter;
+ public string Alias => "move";
+ public string Category => Constants.Conventions.PermissionCategories.StructureCategory;
+ public string Icon => "enter";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionNew.cs b/src/Umbraco.Web/Actions/ActionNew.cs
new file mode 100644
index 0000000000..c07580b42a
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionNew.cs
@@ -0,0 +1,23 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked upon creation of a document
+ ///
+ public class ActionNew : IAction
+ {
+ public const string ActionAlias = "create";
+ public const char ActionLetter = 'C';
+
+ public char Letter => ActionLetter;
+ public string Alias => ActionAlias;
+ public string Icon => "add";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionProtect.cs b/src/Umbraco.Web/Actions/ActionProtect.cs
new file mode 100644
index 0000000000..0e5f9f8433
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionProtect.cs
@@ -0,0 +1,20 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when a document is protected or unprotected
+ ///
+ public class ActionProtect : IAction
+ {
+ public char Letter => 'P';
+ public string Alias => "protect";
+ public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory;
+ public string Icon => "lock";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionPublish.cs b/src/Umbraco.Web/Actions/ActionPublish.cs
new file mode 100644
index 0000000000..5c9ce08c35
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionPublish.cs
@@ -0,0 +1,21 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when a document is being published
+ ///
+ public class ActionPublish : IAction
+ {
+ public const char ActionLetter = 'U';
+
+ public char Letter => ActionLetter;
+ public string Alias => "publish";
+ public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
+ public string Icon => string.Empty;
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionRestore.cs b/src/Umbraco.Web/Actions/ActionRestore.cs
new file mode 100644
index 0000000000..aa309131f2
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionRestore.cs
@@ -0,0 +1,20 @@
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when the content/media item is to be restored from the recycle bin
+ ///
+ public class ActionRestore : IAction
+ {
+ public const string ActionAlias = "restore";
+
+ public char Letter => 'V';
+ public string Alias => ActionAlias;
+ public string Category => null;
+ public string Icon => "undo";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => false;
+
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionRights.cs b/src/Umbraco.Web/Actions/ActionRights.cs
new file mode 100644
index 0000000000..0d9ace918e
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionRights.cs
@@ -0,0 +1,20 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when rights are changed on a document
+ ///
+ public class ActionRights : IAction
+ {
+ public char Letter => 'R';
+ public string Alias => "rights";
+ public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
+ public string Icon => "vcard";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionRollback.cs b/src/Umbraco.Web/Actions/ActionRollback.cs
new file mode 100644
index 0000000000..96ce1e7767
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionRollback.cs
@@ -0,0 +1,22 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when copying a document is being rolled back
+ ///
+ public class ActionRollback : IAction
+ {
+ public const char ActionLetter = 'K';
+
+ public char Letter => ActionLetter;
+ public string Alias => "rollback";
+ public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory;
+ public string Icon => "undo";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionSort.cs b/src/Umbraco.Web/Actions/ActionSort.cs
new file mode 100644
index 0000000000..8b4e01823e
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionSort.cs
@@ -0,0 +1,19 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when children to a document, media, member is being sorted
+ ///
+ public class ActionSort : IAction
+ {
+ public char Letter => 'S';
+ public string Alias => "sort";
+ public string Category => Constants.Conventions.PermissionCategories.StructureCategory;
+ public string Icon => "navigation-vertical";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionToPublish.cs b/src/Umbraco.Web/Actions/ActionToPublish.cs
new file mode 100644
index 0000000000..518c82ab87
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionToPublish.cs
@@ -0,0 +1,22 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when children to a document is being sent to published (by an editor without publishrights)
+ ///
+ public class ActionToPublish : IAction
+ {
+ public const char ActionLetter = 'H';
+
+ public char Letter => ActionLetter;
+ public string Alias => "sendtopublish";
+ public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
+ public string Icon => "outbox";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/ActionUnpublish.cs b/src/Umbraco.Web/Actions/ActionUnpublish.cs
new file mode 100644
index 0000000000..8ece4c008e
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionUnpublish.cs
@@ -0,0 +1,21 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+
+
+namespace Umbraco.Web.Actions
+{
+
+ ///
+ /// This action is invoked when a document is being unpublished
+ ///
+ public class ActionUnpublish : IAction
+ {
+ public char Letter => 'Z';
+ public string Alias => "unpublish";
+ public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
+ public string Icon => "circle-dotted";
+ public bool ShowInNotifier => false;
+ public bool CanBePermissionAssigned => true;
+ }
+
+}
diff --git a/src/Umbraco.Web/Actions/ActionUpdate.cs b/src/Umbraco.Web/Actions/ActionUpdate.cs
new file mode 100644
index 0000000000..a2fba0fd89
--- /dev/null
+++ b/src/Umbraco.Web/Actions/ActionUpdate.cs
@@ -0,0 +1,22 @@
+using Umbraco.Core;
+using Umbraco.Core.CodeAnnotations;
+using Umbraco.Web.UI.Pages;
+
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// This action is invoked when copying a document or media
+ ///
+ public class ActionUpdate : IAction
+ {
+ public const char ActionLetter = 'A';
+
+ public char Letter => ActionLetter;
+ public string Alias => "update";
+ public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
+ public string Icon => "save";
+ public bool ShowInNotifier => true;
+ public bool CanBePermissionAssigned => true;
+ }
+}
diff --git a/src/Umbraco.Web/Actions/IAction.cs b/src/Umbraco.Web/Actions/IAction.cs
new file mode 100644
index 0000000000..986ed9b509
--- /dev/null
+++ b/src/Umbraco.Web/Actions/IAction.cs
@@ -0,0 +1,46 @@
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Web.Actions
+{
+ ///
+ /// Defines a back office action that can be permission assigned or subscribed to for notifications
+ ///
+ ///
+ /// If an IAction returns false for both ShowInNotifier and CanBePermissionAssigned then the IAction should not exist
+ ///
+ public interface IAction : IDiscoverable
+ {
+ ///
+ /// The letter used to assign a permission (must be unique)
+ ///
+ char Letter { get; }
+
+ ///
+ /// Whether to allow subscribing to notifications for this action
+ ///
+ bool ShowInNotifier { get; }
+
+ ///
+ /// Whether to allow assigning permissions based on this action
+ ///
+ bool CanBePermissionAssigned { get; }
+
+ ///
+ /// The icon to display for this action
+ ///
+ string Icon { get; }
+
+ ///
+ /// The alias for this action (must be unique)
+ ///
+ string Alias { get; }
+
+ ///
+ /// The category used for this action
+ ///
+ ///
+ /// Used in the UI when assigning permissions
+ ///
+ string Category { get; }
+ }
+}
diff --git a/src/Umbraco.Web/Components/NotificationsComponent.cs b/src/Umbraco.Web/Components/NotificationsComponent.cs
index 27fc604d29..79f5ee9f6d 100644
--- a/src/Umbraco.Web/Components/NotificationsComponent.cs
+++ b/src/Umbraco.Web/Components/NotificationsComponent.cs
@@ -5,23 +5,24 @@ using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
-using Umbraco.Web._Legacy.Actions;
+using Umbraco.Web.Actions;
+
namespace Umbraco.Web.Components
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public sealed class NotificationsComponent : UmbracoComponentBase, IUmbracoCoreComponent
{
- public void Initialize(INotificationService notificationService)
+ public void Initialize(INotificationService notificationService, ActionCollection actions)
{
ContentService.SentToPublish += (sender, args) =>
- notificationService.SendNotification(args.Entity, ActionToPublish.Instance);
+ notificationService.SendNotification(args.Entity, actions.GetAction());
//Send notifications for the published action
ContentService.Published += (sender, args) =>
{
foreach (var content in args.PublishedEntities)
- notificationService.SendNotification(content, ActionPublish.Instance);
+ notificationService.SendNotification(content, actions.GetAction());
};
//Send notifications for the update and created actions
@@ -45,22 +46,22 @@ namespace Umbraco.Web.Components
updatedEntities.Add(entity);
}
}
- notificationService.SendNotification(newEntities, ActionNew.Instance);
- notificationService.SendNotification(updatedEntities, ActionUpdate.Instance);
+ notificationService.SendNotification(newEntities, actions.GetAction());
+ notificationService.SendNotification(updatedEntities, actions.GetAction());
};
//Send notifications for the delete action
ContentService.Deleted += (sender, args) =>
{
foreach (var content in args.DeletedEntities)
- notificationService.SendNotification(content, ActionDelete.Instance);
+ notificationService.SendNotification(content, actions.GetAction());
};
//Send notifications for the unpublish action
ContentService.Unpublished += (sender, args) =>
{
foreach (var content in args.PublishedEntities)
- notificationService.SendNotification(content, ActionUnpublish.Instance);
+ notificationService.SendNotification(content, actions.GetAction());
};
}
}
diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs
index 3e3f6c75a6..839254f03c 100644
--- a/src/Umbraco.Web/Composing/Current.cs
+++ b/src/Umbraco.Web/Composing/Current.cs
@@ -19,6 +19,7 @@ using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Core._Legacy.PackageActions;
+using Umbraco.Web.Actions;
using Umbraco.Web.Cache;
using Umbraco.Web.Editors;
using Umbraco.Web.HealthCheck;
@@ -27,7 +28,7 @@ using Umbraco.Web.Mvc;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.WebApi;
-using Umbraco.Web._Legacy.Actions;
+
using CoreCurrent = Umbraco.Core.Composing.Current;
namespace Umbraco.Web.Composing
diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/CompositionExtensions.cs
index f33c8e98a8..38b0be3103 100644
--- a/src/Umbraco.Web/CompositionExtensions.cs
+++ b/src/Umbraco.Web/CompositionExtensions.cs
@@ -3,13 +3,13 @@ using LightInject;
using Umbraco.Core.Composing;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Core.Macros;
+using Umbraco.Web.Actions;
using Umbraco.Web.Editors;
using Umbraco.Web.HealthCheck;
using Umbraco.Web.Media;
using Umbraco.Web.Mvc;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
-using Umbraco.Web._Legacy.Actions;
using Umbraco.Web.ContentApps;
// the namespace here is intentional - although defined in Umbraco.Web assembly,
diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs
index 15554b7f50..7d0cc26d15 100644
--- a/src/Umbraco.Web/Editors/BackOfficeController.cs
+++ b/src/Umbraco.Web/Editors/BackOfficeController.cs
@@ -3,18 +3,15 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
-using LightInject;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
@@ -22,18 +19,13 @@ using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.Identity;
-using Umbraco.Core.Models.Membership;
-using Umbraco.Core.Security;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
-
-using Umbraco.Web.Trees;
using Umbraco.Web.UI.JavaScript;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Features;
using Umbraco.Web.Security;
-using Action = Umbraco.Web._Legacy.Actions.Action;
using Constants = Umbraco.Core.Constants;
using JArray = Newtonsoft.Json.Linq.JArray;
@@ -197,11 +189,8 @@ namespace Umbraco.Web.Editors
{
var initJs = new JsInitialization(_manifestParser);
var initCss = new CssInitialization(_manifestParser);
-
- //get the legacy ActionJs file references to append as well
- var legacyActionJsRef = GetLegacyActionJs(LegacyJsActionType.JsUrl);
-
- var files = initJs.OptimizeBackOfficeScriptFiles(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef);
+
+ var files = initJs.OptimizeBackOfficeScriptFiles(HttpContext, JsInitialization.GetDefaultInitialization());
var result = JsInitialization.GetJavascriptInitialization(HttpContext, files, "umbraco");
result += initCss.GetStylesheetInitialization(HttpContext);
@@ -519,50 +508,7 @@ namespace Umbraco.Web.Editors
}
return true;
}
-
- internal static IEnumerable GetLegacyActionJsForActions(LegacyJsActionType type, IEnumerable values)
- {
- var blockList = new List();
- var urlList = new List();
- foreach (var jsFile in values)
- {
- var isJsPath = jsFile.DetectIsJavaScriptPath();
- if (isJsPath.Success)
-
- {
- urlList.Add(isJsPath.Result);
- }
- else
- {
- blockList.Add(isJsPath.Result);
- }
- }
-
- switch (type)
- {
- case LegacyJsActionType.JsBlock:
- return blockList;
- case LegacyJsActionType.JsUrl:
- return urlList;
- }
-
- return blockList;
- }
-
- ///
- /// Renders out all JavaScript references that have been declared in IActions
- ///
- private static IEnumerable GetLegacyActionJs(LegacyJsActionType type)
- {
- return GetLegacyActionJsForActions(type, Action.GetJavaScriptFileReferences());
- }
-
- internal enum LegacyJsActionType
- {
- JsBlock,
- JsUrl
- }
-
+
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs
index 9681a79ed1..03bfc78204 100644
--- a/src/Umbraco.Web/Editors/ContentController.cs
+++ b/src/Umbraco.Web/Editors/ContentController.cs
@@ -30,10 +30,11 @@ using Umbraco.Core.Models.Validation;
using Umbraco.Web.Composing;
using Umbraco.Web.Models;
using Umbraco.Web.WebServices;
-using Umbraco.Web._Legacy.Actions;
+
using Constants = Umbraco.Core.Constants;
using Language = Umbraco.Web.Models.ContentEditing.Language;
using Umbraco.Core.PropertyEditors;
+using Umbraco.Web.Actions;
using Umbraco.Web.ContentApps;
using Umbraco.Web.Editors.Binders;
using Umbraco.Web.Editors.Filters;
@@ -456,7 +457,7 @@ namespace Umbraco.Web.Editors
public PagedResult> GetChildren(
int id,
string includeProperties,
- int pageNumber = 0, //TODO: This should be '1' as it's not the index
+ int pageNumber = 0,
int pageSize = 0,
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
@@ -483,7 +484,8 @@ namespace Umbraco.Web.Editors
}
else
{
- children = Services.ContentService.GetChildren(id).ToList();
+ //better to not use this without paging where possible, currently only the sort dialog does
+ children = Services.ContentService.GetPagedChildren(id, 0, int.MaxValue, out var total).ToList();
totalChildren = children.Count;
}
@@ -931,14 +933,14 @@ namespace Umbraco.Web.Editors
var errMsg = Services.TextService.Localize(localizationKey, new[] { _allLangs.Value[culture].CultureName });
ModelState.AddModelError(key, errMsg);
}
-
+
///
/// Publishes a document with a given ID
///
///
///
///
- /// The CanAccessContentAuthorize attribute will deny access to this method if the current user
+ /// The EnsureUserPermissionForContent attribute will deny access to this method if the current user
/// does not have Publish access to this node.
///
///
@@ -1076,7 +1078,7 @@ namespace Umbraco.Web.Editors
if (sorted.ParentId > 0)
{
- Services.NotificationService.SendNotification(contentService.GetById(sorted.ParentId), ActionSort.Instance, UmbracoContext, Services.TextService, GlobalSettings);
+ Services.NotificationService.SendNotification(contentService.GetById(sorted.ParentId), Current.Actions.GetAction(), UmbracoContext, Services.TextService, GlobalSettings);
}
return Request.CreateResponse(HttpStatusCode.OK);
@@ -1223,7 +1225,7 @@ namespace Umbraco.Web.Editors
var permission = Services.UserService.GetPermissions(Security.CurrentUser, node.Path);
- if (permission.AssignedPermissions.Contains(ActionAssignDomain.Instance.Letter.ToString(), StringComparer.Ordinal) == false)
+ if (permission.AssignedPermissions.Contains(ActionAssignDomain.ActionLetter.ToString(), StringComparer.Ordinal) == false)
{
var response = Request.CreateResponse(HttpStatusCode.BadRequest);
response.Content = new StringContent("You do not have permission to assign domains on that node.");
@@ -1317,7 +1319,7 @@ namespace Umbraco.Web.Editors
xnames.Add(xcontent.Name);
if (xcontent.ParentId < -1)
xnames.Add("Recycle Bin");
- xcontent = xcontent.Parent(Services.ContentService);
+ xcontent = Services.ContentService.GetParent(xcontent);
}
xnames.Reverse();
domainModel.Other = "/" + string.Join("/", xnames);
@@ -1760,6 +1762,7 @@ namespace Umbraco.Web.Editors
: content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture);
}
+ [EnsureUserPermissionForContent("contentId", ActionRollback.ActionLetter)]
[HttpPost]
public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*")
{
diff --git a/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs
index ed6e15ed09..2698986bcd 100644
--- a/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs
+++ b/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs
@@ -9,11 +9,12 @@ using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
+using Umbraco.Web.Actions;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Security;
using Umbraco.Web.WebApi;
-using Umbraco.Web._Legacy.Actions;
+
namespace Umbraco.Web.Editors.Filters
{
@@ -94,24 +95,24 @@ namespace Umbraco.Web.Editors.Filters
switch (contentItem.Action)
{
case ContentSaveAction.Save:
- permissionToCheck.Add(ActionUpdate.Instance.Letter);
+ permissionToCheck.Add(ActionUpdate.ActionLetter);
contentToCheck = contentItem.PersistedContent;
contentIdToCheck = contentToCheck.Id;
break;
case ContentSaveAction.Publish:
- permissionToCheck.Add(ActionPublish.Instance.Letter);
+ permissionToCheck.Add(ActionPublish.ActionLetter);
contentToCheck = contentItem.PersistedContent;
contentIdToCheck = contentToCheck.Id;
break;
case ContentSaveAction.SendPublish:
- permissionToCheck.Add(ActionToPublish.Instance.Letter);
+ permissionToCheck.Add(ActionToPublish.ActionLetter);
contentToCheck = contentItem.PersistedContent;
contentIdToCheck = contentToCheck.Id;
break;
case ContentSaveAction.SaveNew:
//Save new requires ActionNew
- permissionToCheck.Add(ActionNew.Instance.Letter);
+ permissionToCheck.Add(ActionNew.ActionLetter);
if (contentItem.ParentId != Constants.System.Root)
{
@@ -126,8 +127,8 @@ namespace Umbraco.Web.Editors.Filters
case ContentSaveAction.SendPublishNew:
//Send new requires both ActionToPublish AND ActionNew
- permissionToCheck.Add(ActionNew.Instance.Letter);
- permissionToCheck.Add(ActionToPublish.Instance.Letter);
+ permissionToCheck.Add(ActionNew.ActionLetter);
+ permissionToCheck.Add(ActionToPublish.ActionLetter);
if (contentItem.ParentId != Constants.System.Root)
{
contentToCheck = _contentService.GetById(contentItem.ParentId);
@@ -142,8 +143,8 @@ namespace Umbraco.Web.Editors.Filters
//Publish new requires both ActionNew AND ActionPublish
//TODO: Shoudn't publish also require ActionUpdate since it will definitely perform an update to publish but maybe that's just implied
- permissionToCheck.Add(ActionNew.Instance.Letter);
- permissionToCheck.Add(ActionPublish.Instance.Letter);
+ permissionToCheck.Add(ActionNew.ActionLetter);
+ permissionToCheck.Add(ActionPublish.ActionLetter);
if (contentItem.ParentId != Constants.System.Root)
{
diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs
index dc744ea361..f61cbc5952 100644
--- a/src/Umbraco.Web/Editors/MediaController.cs
+++ b/src/Umbraco.Web/Editors/MediaController.cs
@@ -209,7 +209,10 @@ namespace Umbraco.Web.Editors
}
long total;
- var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total, "Name", Direction.Ascending, true, null, folderTypes.ToArray());
+ var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total,
+ //lookup these content types
+ SqlContext.Query().Where(x => folderTypes.Contains(x.ContentTypeId)),
+ Ordering.By("Name", Direction.Ascending));
return new PagedResult>(total, pageNumber, pageSize)
{
@@ -271,7 +274,7 @@ namespace Umbraco.Web.Editors
// else proceed as usual
long totalChildren;
- IMedia[] children;
+ List children;
if (pageNumber > 0 && pageSize > 0)
{
IQuery queryFilter = null;
@@ -286,13 +289,14 @@ namespace Umbraco.Web.Editors
.GetPagedChildren(
id, (pageNumber - 1), pageSize,
out totalChildren,
- orderBy, orderDirection, orderBySystemField,
- queryFilter).ToArray();
+ queryFilter,
+ Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)).ToList();
}
else
{
- children = Services.MediaService.GetChildren(id).ToArray();
- totalChildren = children.Length;
+ //better to not use this without paging where possible, currently only the sort dialog does
+ children = Services.MediaService.GetPagedChildren(id, 0, int.MaxValue, out var total).ToList();
+ totalChildren = children.Count;
}
if (totalChildren == 0)
@@ -663,7 +667,8 @@ namespace Umbraco.Web.Editors
" returned null");
//look for matching folder
- folderMediaItem = mediaRoot.Children(Services.MediaService).FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder);
+ folderMediaItem = FindInChildren(mediaRoot.Id, folderName, Constants.Conventions.MediaTypes.Folder);
+
if (folderMediaItem == null)
{
//if null, create a folder
@@ -753,6 +758,21 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(HttpStatusCode.OK, tempFiles);
}
+ private IMedia FindInChildren(int mediaId, string nameToFind, string contentTypeAlias)
+ {
+ const int pageSize = 500;
+ var page = 0;
+ var total = long.MaxValue;
+ while (page * pageSize < total)
+ {
+ var children = Services.MediaService.GetPagedChildren(mediaId, page, pageSize, out total,
+ SqlContext.Query().Where(x => x.Name == nameToFind));
+ foreach (var c in children)
+ return c; //return first one if any are found
+ }
+ return null;
+ }
+
///
/// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT
///
diff --git a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs
index a627eab184..7db491ad2e 100644
--- a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs
+++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs
@@ -52,6 +52,18 @@ namespace Umbraco.Web.Models.Mapping
variant.Name = source.GetCultureName(x.IsoCode);
}
+ //Put the default language first in the list & then sort rest by a-z
+ var defaultLang = variants.SingleOrDefault(x => x.Language.IsDefault);
+
+ //Remove the default lang from the list for now
+ variants.Remove(defaultLang);
+
+ //Sort the remaining languages a-z
+ variants = variants.OrderBy(x => x.Name).ToList();
+
+ //Insert the default lang as the first item
+ variants.Insert(0, defaultLang);
+
return variants;
}
return result;
diff --git a/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs
index b305ee2824..f820d5ae54 100644
--- a/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs
+++ b/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs
@@ -28,7 +28,21 @@ namespace Umbraco.Web.Models.Mapping
{
public IEnumerable Convert(IEnumerable source, IEnumerable destination, ResolutionContext context)
{
- return source.Select(x => context.Mapper.Map(x, null, context)).OrderBy(x => x.Name);
+ var langs = source.Select(x => context.Mapper.Map(x, null, context)).ToList();
+
+ //Put the default language first in the list & then sort rest by a-z
+ var defaultLang = langs.SingleOrDefault(x => x.IsDefault);
+
+ //Remove the default lang from the list for now
+ langs.Remove(defaultLang);
+
+ //Sort the remaining languages a-z
+ langs = langs.OrderBy(x => x.Name).ToList();
+
+ //Insert the default lang as the first item
+ langs.Insert(0, defaultLang);
+
+ return langs;
}
}
}
diff --git a/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs b/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs
index 89144084e2..b13b5cda10 100644
--- a/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs
+++ b/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs
@@ -6,8 +6,9 @@ using Umbraco.Core;
using Umbraco.Core.CodeAnnotations;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
+using Umbraco.Web.Actions;
using Umbraco.Web.Models.ContentEditing;
-using Umbraco.Web._Legacy.Actions;
+
namespace Umbraco.Web.Models.Mapping
{
@@ -37,13 +38,11 @@ namespace Umbraco.Web.Models.Mapping
private Permission GetPermission(IAction action, IUserGroup source)
{
var result = new Permission();
- var attribute = action.GetType().GetCustomAttribute(false);
- result.Category = attribute == null
+
+ result.Category = action.Category.IsNullOrWhiteSpace()
? _textService.Localize($"actionCategories/{Constants.Conventions.PermissionCategories.OtherCategory}")
- : _textService.Localize($"actionCategories/{attribute.Category}");
- result.Name = attribute == null || attribute.Name.IsNullOrWhiteSpace()
- ? _textService.Localize($"actions/{action.Alias}")
- : attribute.Name;
+ : _textService.Localize($"actionCategories/{action.Category}");
+ result.Name = _textService.Localize($"actions/{action.Alias}");
result.Description = _textService.Localize($"actionDescriptions/{action.Alias}");
result.Icon = action.Icon;
result.Checked = source.Permissions != null && source.Permissions.Contains(action.Letter.ToString(CultureInfo.InvariantCulture));
diff --git a/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs
index 213a1a5e3a..8261dda0d4 100644
--- a/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs
+++ b/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs
@@ -11,7 +11,8 @@ using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
-using Umbraco.Web._Legacy.Actions;
+using Umbraco.Web.Actions;
+
namespace Umbraco.Web.Models.Mapping
{
diff --git a/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs b/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs
index b2fb479771..9354417155 100644
--- a/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs
+++ b/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs
@@ -1,41 +1,55 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Umbraco.Core;
+using Umbraco.Core.Services;
namespace Umbraco.Web.Models.Trees
{
- ///
- /// A menu item that represents some JS that needs to execute when the menu item is clicked.
- ///
- ///
- /// These types of menu items are rare but they do exist. Things like refresh node simply execute
- /// JS and don't launch a dialog.
- ///
- /// Each action menu item describes what angular service that it's method exists in and what the method name is.
- ///
- /// An action menu item must describe the angular service name for which it's method exists. It may also define what the
- /// method name is that will be called in this service but if one is not specified then we will assume the method name is the
- /// same as the Type name of the current action menu class.
- ///
+ ///
+ ///
+ /// A menu item that represents some JS that needs to execute when the menu item is clicked.
+ ///
+ ///
+ /// These types of menu items are rare but they do exist. Things like refresh node simply execute
+ /// JS and don't launch a dialog.
+ /// Each action menu item describes what angular service that it's method exists in and what the method name is.
+ /// An action menu item must describe the angular service name for which it's method exists. It may also define what the
+ /// method name is that will be called in this service but if one is not specified then we will assume the method name is the
+ /// same as the Type name of the current action menu class.
+ ///
public abstract class ActionMenuItem : MenuItem
{
- protected ActionMenuItem()
- : base()
- {
- var attribute = GetType().GetCustomAttribute(false);
- if (attribute == null)
- {
- throw new InvalidOperationException("All " + typeof (ActionMenuItem).FullName + " instances must be attributed with " + typeof (ActionMenuItemAttribute).FullName);
- }
+ ///
+ /// The angular service name containing the
+ ///
+ public abstract string AngularServiceName { get; }
+ ///
+ /// The angular service method name to call for this menu item
+ ///
+ public virtual string AngularServiceMethodName { get; } = null;
+
+ 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 (attribute.MethodName.IsNullOrWhiteSpace())
+ if (AngularServiceMethodName.IsNullOrWhiteSpace())
{
//if no method name is supplied we will assume that the menu action is the type name of the current menu class
- AdditionalData.Add("jsAction", string.Format("{0}.{1}", attribute.ServiceName, this.GetType().Name));
+ ExecuteJsMethod($"{AngularServiceName}.{this.GetType().Name}");
}
else
{
- AdditionalData.Add("jsAction", string.Format("{0}.{1}", attribute.ServiceName, attribute.MethodName));
+ ExecuteJsMethod($"{AngularServiceName}.{AngularServiceMethodName}");
}
}
}
diff --git a/src/Umbraco.Web/Models/Trees/ActionMenuItemAttribute.cs b/src/Umbraco.Web/Models/Trees/ActionMenuItemAttribute.cs
deleted file mode 100644
index 64558b8727..0000000000
--- a/src/Umbraco.Web/Models/Trees/ActionMenuItemAttribute.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using Umbraco.Core.Exceptions;
-
-namespace Umbraco.Web.Models.Trees
-{
- ///
- /// The attribute to assign to any ActionMenuItem objects.
- ///
- [AttributeUsage(AttributeTargets.Class)]
- public sealed class ActionMenuItemAttribute : Attribute
- {
- ///
- /// This constructor defines both the angular service and method name to use
- ///
- ///
- ///
- public ActionMenuItemAttribute(string serviceName, string methodName)
- {
- if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName));
- if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentNullOrEmptyException(nameof(methodName));
- MethodName = methodName;
- ServiceName = serviceName;
- }
-
- ///
- /// This constructor will assume that the method name equals the type name of the action menu class
- ///
- ///
- public ActionMenuItemAttribute(string serviceName)
- {
- if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName));
- MethodName = "";
- ServiceName = serviceName;
- }
-
- public string MethodName { get; }
- public string ServiceName { get; }
- }
-}
diff --git a/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs b/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs
index ca89851beb..022056c35d 100644
--- a/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs
+++ b/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs
@@ -1,10 +1,27 @@
-namespace Umbraco.Web.Models.Trees
+using Umbraco.Core.Services;
+using Umbraco.Web.Actions;
+
+namespace Umbraco.Web.Models.Trees
{
///
/// Represents the refresh node menu item
///
- [ActionMenuItem("umbracoMenuActions")]
public sealed class CreateChildEntity : ActionMenuItem
{
+ public override string AngularServiceName => "umbracoMenuActions";
+
+ public CreateChildEntity(string name, bool seperatorBefore = false)
+ : base(ActionNew.ActionAlias, name)
+ {
+ Icon = "add"; Name = name;
+ SeperatorBefore = seperatorBefore;
+ }
+
+ public CreateChildEntity(ILocalizedTextService textService, bool seperatorBefore = false)
+ : base(ActionNew.ActionAlias, textService)
+ {
+ Icon = "add";
+ SeperatorBefore = seperatorBefore;
+ }
}
}
diff --git a/src/Umbraco.Web/Models/Trees/DisableUser.cs b/src/Umbraco.Web/Models/Trees/DisableUser.cs
deleted file mode 100644
index 3602897ac2..0000000000
--- a/src/Umbraco.Web/Models/Trees/DisableUser.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Umbraco.Web.Models.Trees
-{
- ///
- /// Represents the disable user menu item
- ///
- [ActionMenuItem("umbracoMenuActions")]
- public sealed class DisableUser : ActionMenuItem
- {
- public DisableUser()
- {
- Alias = "disable";
- Icon = "remove";
- }
- }
-}
diff --git a/src/Umbraco.Web/Models/Trees/ExportMember.cs b/src/Umbraco.Web/Models/Trees/ExportMember.cs
index 66a10da007..558f7c1fa1 100644
--- a/src/Umbraco.Web/Models/Trees/ExportMember.cs
+++ b/src/Umbraco.Web/Models/Trees/ExportMember.cs
@@ -1,9 +1,17 @@
-namespace Umbraco.Web.Models.Trees
+using Umbraco.Core.Services;
+
+namespace Umbraco.Web.Models.Trees
{
///
/// Represents the export member menu item
///
- [ActionMenuItem("umbracoMenuActions")]
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 412cd9106d..4170cdb73f 100644
--- a/src/Umbraco.Web/Models/Trees/MenuItem.cs
+++ b/src/Umbraco.Web/Models/Trees/MenuItem.cs
@@ -5,8 +5,9 @@ using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Services;
+using Umbraco.Web.Actions;
using Umbraco.Web.Composing;
-using Umbraco.Web._Legacy.Actions;
+
namespace Umbraco.Web.Models.Trees
{
@@ -30,15 +31,27 @@ namespace Umbraco.Web.Models.Trees
Name = name;
}
- public MenuItem(IAction legacyMenu, string name = "")
+
+ public MenuItem(string alias, ILocalizedTextService textService)
: this()
{
- Name = name.IsNullOrWhiteSpace() ? legacyMenu.Alias : name;
- Alias = legacyMenu.Alias;
+ Alias = alias;
+ Name = textService.Localize($"actions/{Alias}");
+ }
+
+ ///
+ /// Create a menu item based on an definition
+ ///
+ ///
+ ///
+ public MenuItem(IAction action, string name = "")
+ : this()
+ {
+ Name = name.IsNullOrWhiteSpace() ? action.Alias : name;
+ Alias = action.Alias;
SeperatorBefore = false;
- Icon = legacyMenu.Icon;
- Action = legacyMenu;
- OpensDialog = legacyMenu.OpensDialog;
+ Icon = action.Icon;
+ Action = action;
}
#endregion
@@ -73,6 +86,9 @@ namespace Umbraco.Web.Models.Trees
[DataMember(Name = "cssclass")]
public string Icon { get; set; }
+ ///
+ /// Used in the UI to inform the user that the menu item will open a dialog/confirmation
+ ///
[DataMember(Name = "opensDialog")]
public bool OpensDialog { get; set; }
@@ -128,7 +144,7 @@ namespace Umbraco.Web.Models.Trees
/// Adds the required meta data to the menu item so that angular knows to attempt to call the Js method.
///
///
- public void ExecuteLegacyJs(string jsToExecute)
+ public void ExecuteJsMethod(string jsToExecute)
{
SetJsAction(jsToExecute);
}
@@ -206,7 +222,7 @@ namespace Umbraco.Web.Models.Trees
}
}
}
-
+
#endregion
}
diff --git a/src/Umbraco.Web/Models/Trees/MenuItemList.cs b/src/Umbraco.Web/Models/Trees/MenuItemList.cs
index 170042b151..b34f0b4444 100644
--- a/src/Umbraco.Web/Models/Trees/MenuItemList.cs
+++ b/src/Umbraco.Web/Models/Trees/MenuItemList.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using Umbraco.Core;
+using Umbraco.Core.Services;
+using Umbraco.Web.Actions;
using Umbraco.Web.Composing;
-using Umbraco.Web._Legacy.Actions;
+
namespace Umbraco.Web.Models.Trees
{
@@ -24,7 +26,7 @@ namespace Umbraco.Web.Models.Trees
}
///
- /// Adds a menu item
+ /// Adds a menu item based on a
///
///
/// The text to display for the menu item, will default to the IAction alias if not specified
@@ -32,78 +34,20 @@ namespace Umbraco.Web.Models.Trees
{
var item = new MenuItem(action, name);
- DetectLegacyActionMenu(action.GetType(), item);
-
Add(item);
return item;
}
- ///
- /// Adds a menu item
- ///
- ///
- ///
- ///
- /// The text to display for the menu item, will default to the IAction alias if not specified
- ///
- ///
- public TMenuItem Add(string name, bool hasSeparator = false, IDictionary additionalData = null)
- where TAction : IAction
- where TMenuItem : MenuItem, new()
- {
- var item = CreateMenuItem(name, hasSeparator, additionalData);
- if (item == null) return null;
-
- var customMenuItem = new TMenuItem
- {
- Name = item.Name,
- Alias = item.Alias,
- SeperatorBefore = hasSeparator,
- Icon = item.Icon,
- Action = item.Action
- };
-
- Add(customMenuItem);
-
- return customMenuItem;
- }
-
- ///
- /// Adds a menu item
- ///
- /// 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, null);
- }
-
- ///
- /// Adds a menu item with a key value pair which is merged to the AdditionalData bag
- ///
- ///
- ///
- ///
- /// The text to display for the menu item, will default to the IAction alias if not specified
- ///
- public MenuItem Add(string name, string key, string value, bool hasSeparator = false)
- where T : IAction
- {
- return Add(name, hasSeparator, new Dictionary { { key, value } });
- }
-
///