diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs index ba19f41e74..cc83dcb1c9 100644 --- a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs @@ -29,5 +29,30 @@ namespace Umbraco.Tests.Web.Mvc var output = _htmlHelper.Wrap("div", "hello world", new {style = "color:red;", onclick = "void();"}); Assert.AreEqual("
hello world
", output.ToHtmlString()); } + + [Test] + public void GetRelatedLinkHtml_Simple() + { + var relatedLink = new Umbraco.Web.Models.RelatedLink { + Caption = "Link Caption", + NewWindow = true, + Link = "https://www.google.com/" + }; + var output = _htmlHelper.GetRelatedLinkHtml(relatedLink); + Assert.AreEqual("Link Caption", output.ToHtmlString()); + } + + [Test] + public void GetRelatedLinkHtml_HtmlAttributes() + { + var relatedLink = new Umbraco.Web.Models.RelatedLink + { + Caption = "Link Caption", + NewWindow = true, + Link = "https://www.google.com/" + }; + var output = _htmlHelper.GetRelatedLinkHtml(relatedLink, new { @class = "test-class"}); + Assert.AreEqual("Link Caption", output.ToHtmlString()); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js index bb1dad1dbd..1fe739154a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/codefile.resource.js @@ -3,7 +3,7 @@ * @name umbraco.resources.codefileResource * @description Loads in data for files that contain code such as js scripts, partial views and partial view macros **/ -function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) { +function codefileResource($q, $http, umbDataFormatter, umbRequestHelper, localizationService) { return { @@ -106,13 +106,16 @@ function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) { * */ deleteByPath: function (type, virtualpath) { + + var promise = localizationService.localize("codefile_deleteItemFailed", [virtualpath]); + return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "codeFileApiBaseUrl", "Delete", [{ type: type }, { virtualPath: virtualpath}])), - "Failed to delete item: " + virtualpath); + promise); }, /** @@ -236,13 +239,19 @@ function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) { * */ - createContainer: function(type, parentId, name) { + createContainer: function (type, parentId, name) { + + // Is the parent ID numeric? + var key = "codefile_createFolderFailedBy" + (isNaN(parseInt(parentId)) ? "Name" : "Id"); + + var promise = localizationService.localize(key, [parentId]); + return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl( "codeFileApiBaseUrl", "PostCreateContainer", { type: type, parentId: parentId, name: encodeURIComponent(name) })), - 'Failed to create a folder under parent id ' + parentId); + promise); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js index f969864ba1..377bb415fc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js @@ -3,7 +3,7 @@ * @name umbraco.resources.templateResource * @description Loads in data for templates **/ -function templateResource($q, $http, umbDataFormatter, umbRequestHelper) { +function templateResource($q, $http, umbDataFormatter, umbRequestHelper, localizationService) { return { @@ -152,13 +152,16 @@ function templateResource($q, $http, umbDataFormatter, umbRequestHelper) { * */ deleteById: function(id) { + + var promise = localizationService.localize("template_deleteByIdFailed", [id]); + return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "templateApiBaseUrl", "DeleteById", [{ id: id }])), - "Failed to delete item " + id); + promise); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html index 1165776be6..3cca62b2b9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html @@ -57,7 +57,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.controller.js index b25ac3c7b8..1e615ad0d4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.controller.js @@ -12,6 +12,9 @@ function PartialViewsDeleteController($scope, codefileResource, treeService, nav //mark it for deletion (used in the UI) $scope.currentNode.loading = true; + + // Reset the error message + $scope.error = null; codefileResource.deleteByPath('partialViews', $scope.currentNode.id) .then(function() { @@ -21,6 +24,9 @@ function PartialViewsDeleteController($scope, codefileResource, treeService, nav //TODO: Need to sync tree, etc... treeService.removeNode($scope.currentNode); navigationService.hideMenu(); + }, function (err) { + $scope.currentNode.loading = false; + $scope.error = err; }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html b/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html index 0f75e8514e..c0fdf2c77f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html @@ -1,6 +1,13 @@
+
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
+
+

Are you sure you want to delete {{currentNode.name}} ?

diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/delete.controller.js index 8995cb1a31..d019a44a10 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/delete.controller.js @@ -12,6 +12,10 @@ function TemplatesDeleteController($scope, templateResource , treeService, navig //mark it for deletion (used in the UI) $scope.currentNode.loading = true; + + // Reset the error message + $scope.error = null; + templateResource.deleteById($scope.currentNode.id).then(function () { $scope.currentNode.loading = false; @@ -21,6 +25,9 @@ function TemplatesDeleteController($scope, templateResource , treeService, navig //TODO: Need to sync tree, etc... treeService.removeNode($scope.currentNode); navigationService.hideMenu(); + }, function (err) { + $scope.currentNode.loading = false; + $scope.error = err; }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/delete.html b/src/Umbraco.Web.UI.Client/src/views/templates/delete.html index 34648aa43e..c98677f764 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/delete.html @@ -1,11 +1,18 @@
+
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
+
+

Are you sure you want to delete {{currentNode.name}} ?

- +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 383dc855fd..7a370ba90e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -180,6 +180,11 @@ Overførsel af egenskaber kunne ikke fuldføres, da en eller flere egenskaber er indstillet til at blive overført mere end én gang. Kun andre dokumenttyper, der er gyldige på denne placering, vises. + + Oprettelse af mappen under parent med ID %0% fejlede + Oprettelse af mappen under parent med navnet %0% fejlede + Sletning af filen/mappen fejlede: %0% + Udgivet Om siden @@ -276,6 +281,7 @@ Hvor ønsker du at oprette den nye %0% Opret under Vælg den dokumenttype, du vil oprette en indholdsskabelon til + Angiv et navn for mappen Vælg en type og skriv en titel "dokument typer".]]> "media typer".]]> @@ -1062,6 +1068,7 @@ Mange hilsner fra Umbraco robotten Editor + Sletning af skabelonen med ID %0% fejlede Rediger skabelon Sektioner Indsæt indholdsområde diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 993286f4c6..02b10fe22f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -189,6 +189,11 @@ Could not complete property mapping as one or more properties have more than one mapping defined. Only alternate types valid for the current location are displayed. + + Failed to create a folder under parent with ID %0% + Failed to create a folder under parent with name %0% + Failed to delete item: %0% + Is Published About this page @@ -291,6 +296,7 @@ Where do you want to create the new %0% Create an item under Select the document type you want to make a content template for + Enter a folder name Choose a type and a title "document types".]]> "media types".]]> @@ -1347,6 +1353,7 @@ To manage your website, simply open the Umbraco back office and start adding con Editor + Failed to delete template with ID %0% Edit template Sections Insert content area 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 b15ff24729..83d6b9224b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -196,6 +196,11 @@ Could not complete property mapping as one or more properties have more than one mapping defined. Only alternate types valid for the current location are displayed. + + Failed to create a folder under parent with ID %0% + Failed to create a folder under parent with name %0% + Failed to delete item: %0% + Is Published About this page @@ -316,6 +321,7 @@ Where do you want to create the new %0% Create an item under Select the document type you want to make a content template for + Enter a folder name Choose a type and a title "document types".]]> "media types".]]> @@ -1387,6 +1393,7 @@ To manage your website, simply open the Umbraco back office and start adding con How the text will look like in the rich text editor. + Failed to delete template with ID %0% Edit template Sections Insert content area diff --git a/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs b/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs index 2b2bf337de..985c42bbbf 100644 --- a/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs +++ b/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs @@ -53,6 +53,16 @@ namespace Umbraco.Web.Editors.Filters if (currentUser.IsAdmin()) return Attempt.Succeed(); + var existingGroups = _userService.GetUserGroupsByAlias(groupAliases); + + if(!existingGroups.Any()) + { + // We're dealing with new groups, + // so authorization should be given to any user with access to Users section + if (currentUser.AllowedSections.Contains(Constants.Applications.Users)) + return Attempt.Succeed(); + } + var userGroups = currentUser.Groups.Select(x => x.Alias).ToArray(); var missingAccess = groupAliases.Except(userGroups).ToArray(); return missingAccess.Length == 0 diff --git a/src/Umbraco.Web/Editors/UserGroupsController.cs b/src/Umbraco.Web/Editors/UserGroupsController.cs index 677890bcc4..5069d99cfe 100644 --- a/src/Umbraco.Web/Editors/UserGroupsController.cs +++ b/src/Umbraco.Web/Editors/UserGroupsController.cs @@ -31,6 +31,7 @@ namespace Umbraco.Web.Editors //authorize that the user has access to save this user group var authHelper = new UserGroupEditorAuthorizationHelper( Services.UserService, Services.ContentService, Services.MediaService, Services.EntityService); + var isAuthorized = authHelper.AuthorizeGroupAccess(Security.CurrentUser, userGroupSave.Alias); if (isAuthorized == false) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, isAuthorized.Result)); @@ -51,6 +52,14 @@ namespace Umbraco.Web.Editors if (isAuthorized == false) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, isAuthorized.Result)); + //current user needs to be added to a new group if not an admin (possibly only if no other users are added?) to avoid a 401 + if(!Security.CurrentUser.IsAdmin() && (userGroupSave.Id == null || Convert.ToInt32(userGroupSave.Id) >= 0)/* && !userGroupSave.Users.Any() */) + { + var userIds = userGroupSave.Users.ToList(); + userIds.Add(Security.CurrentUser.Id); + userGroupSave.Users = userIds; + } + //save the group Services.UserService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users.ToArray()); diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 2e82fe252d..9c30f74ea7 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; +using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; @@ -831,5 +832,37 @@ namespace Umbraco.Web #endregion + #region RelatedLink + + /// + /// Renders an anchor element for a RelatedLink instance. + /// Format: <a href="relatedLink.Link" target="_blank/_self">relatedLink.Caption</a> + /// + /// The HTML helper instance that this method extends. + /// The RelatedLink instance + /// An anchor element + public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink) + { + return htmlHelper.GetRelatedLinkHtml(relatedLink, null); + } + + /// + /// Renders an anchor element for a RelatedLink instance, accepting htmlAttributes. + /// Format: <a href="relatedLink.Link" target="_blank/_self" htmlAttributes>relatedLink.Caption</a> + /// + /// The HTML helper instance that this method extends. + /// The RelatedLink instance + /// An object that contains the HTML attributes to set for the element. + /// + public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink, object htmlAttributes) + { + var tagBuilder = new TagBuilder("a"); + tagBuilder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); + tagBuilder.MergeAttribute("href", relatedLink.Link); + tagBuilder.MergeAttribute("target", relatedLink.NewWindow ? "_blank" : "_self"); + tagBuilder.InnerHtml = HttpUtility.HtmlEncode(relatedLink.Caption); + return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal)); + } + #endregion } }