Disable Save Button in Members Area for Users that are not in a given UserGroup
I have a requirement to provide read-only access to Member data for users other than membership administrators. This has been achieved and details can be found in the thread Intercepting a Core Directive in Angular thanks to some help from Dave Woestenborghs.
There is, however, one current piece of the puzzle yet to solve and that is removing the ability to "Save" a member for users not in a given user group.
I know I can probably cancel the save event but I'm the sort of person that would prefer prevention rather than cure and so I'd rather not present users with a feature that is of no use or concern to them.
Should I be looking at an angular interceptor again to achieve this? It still seems a little hacky.
I never tried it with members but I think it works similar as I got it working for content
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
EditorModelEventManager.SendingContentModel += this.EditorModelEventManager_SendingContentModel;
}
private void EditorModelEventManager_SendingContentModel(System.Web.Http.Filters.HttpActionExecutedContext sender, EditorModelEventArgs<Umbraco.Web.Models.ContentEditing.ContentItemDisplay> e)
{
var contentItemDisplay = e.Model;
var context = e.UmbracoContext;
if (contentItemDisplay.ContentTypeAlias == "MyDocType")
{
contentItemDisplay.AllowedActions = contentItemDisplay.AllowedActions.ToList().Where(x => x != "A" && x != "U");
}
}
This removes the 'Save' and 'Save & Publish' button from a doctype with a specific content type. In the examples from the other thread you can figure out on how to check on user group.
Just had this example lying around. Don't see it can be done using c# code.
So the only option is to use a interceptor.
Maybe it's possible without actually replacing the entire view. Here nathan woulfe has an example on how to change the html using a interceptor without swapping a view
Morning David, I've made good progress on this now and have it working without the check on userGroup so thanks for your help. My angular skills are far from an expert and I am struggling to get access to userService so I can perform the userGroup check. What is the correct way to gain access to the service here?
If I do the following I get a circular dependency error:
(() => {
// This could be done using content events, but that then means the injected directive would need to be compiled
// while this is a bit flaky, it ensures the directive is in the DOM when the digest cycle runs so should be more efficient
// passing the prefix in to the directive allows construction of correct class names
// Ref: https://github.com/nathanwoulfe/NestingContently/blob/master/src/NestingContently/backoffice/interceptor.js
function memberSaveButtonInterceptor($q, userService) {
return {
response: resp => {
if (resp.config.url.toLowerCase().indexOf('views/member/edit.html') !== -1) {
const pattern = /<umb-button[\s\S]*? type="submit"[\s\S]*? label="Save"[\s\S]*? \S+=["']?(?:.(?!["']?\s+(?:\S+)=|[>"']))+.["'][\s\S]*?>[\s\S]*?<\/umb-button>/gmi;
if (resp.data.match(pattern)) {
// Remove the save button in it's entirety
resp.data = resp.data.replace(pattern, '');
//userService.getCurrentUser().then(function (result) {
// return result.userGroups;
//}).then(function (userGroups) {
// console.log(userGroups);
//});
}
}
return resp || $q.when(resp);
}
};
}
// add the interceptor, if it doesn't exist already.
function addInterceptors($httpProvider) {
if ($httpProvider.interceptors.indexOf('memberSaveButtonInterceptor') === -1) {
$httpProvider.interceptors.push('memberSaveButtonInterceptor');
}
}
angular.module('umbraco').factory('memberSaveButtonInterceptor', ['$q', 'userService', memberSaveButtonInterceptor]);
angular.module('umbraco').config(['$httpProvider', addInterceptors]);
})();
Thanks Dave, I've made some further progress. I think I now understand why I was getting the circular dependency errors, see the link in my comments below. This is where I am at now:
angular.module('umbraco.services').config([
'$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push([
'$q', '$injector', function($q, $injector) {
return {
response: resp => {
if (resp.config.url.toLowerCase().indexOf('views/member/edit.html') !== -1) {
// Use the injector to get the userService and avoid circular dependencies
// See https://www.codelord.net/2016/11/10/circular-dependencies-in-angular-and-the-injector-service/
var userService = $injector.get("userService");
userService.getCurrentUser().then(function(result) {
return result.userGroups;
}).then(function(userGroups) {
if (userGroups.indexOf("membershipAdministrator") > -1) {
const pattern = /<umb-button[\s\S]*? type="submit"[\s\S]*? label="Save"[\s\S]*? \S+=["']?(?:.(?!["']?\s+(?:\S+)=|[>"']))+.["'][\s\S]*?>[\s\S]*?<\/umb-button>/gmi;
if (resp.data.match(pattern)) {
// Remove the save button in it's entirety
resp.data = resp.data.replace(pattern, '');
}
}
});
}
return resp || $q.when(resp);
}
};
}
]);
}
]);
I am now fighting a new issue but too tired to make any further meaningful progress tonight. The new errors I think might relate to the callbacks:
The error in my last message is a red herring and doesn't appear to be linked to this work. The only thing I am currently struggling to resolve now is that the resp is returned before the promise is resolved and therefore the button is still appearing in the page. I need a way to stop the function returning until the promise is resolved.
For completeness (for now) I am removing the menu as follows:
$httpProvider.interceptors.push([
'$q', '$injector', function ($q, $injector) {
return {
response: resp => {
if (resp.config.url.toLowerCase().indexOf('views/components/editor/umb-editor-menu.html') !== -1) {
// Use the injector to get the userService and avoid circular dependencies
// See https://www.codelord.net/2016/11/10/circular-dependencies-in-angular-and-the-injector-service/
var userService = $injector.get("userService");
// create deferred request so we don't return the response before our promises have resolved
var deferred = $q.defer();
userService.getCurrentUser().then(function (result) {
return result.userGroups;
}).then(function (userGroups) {
if (userGroups.indexOf("membershipAdministrator") === -1) {
if (resp.data.match(pattern)) {
// Remove the save button in it's entirety
resp.data = resp.data = '';
}
}
// Resolve the promise
deferred.resolve(resp);
});
// return deferred promise
return deferred.promise;
}
return resp || $q.when(resp);
}
};
}
]);
I'm not sure I like either approach to the problem, it feels a bit hacky. There should probably be more granular control over user permissions in the Members section so will consider creating an issue for it on the tracker.
Disable Save Button in Members Area for Users that are not in a given UserGroup
I have a requirement to provide read-only access to Member data for users other than membership administrators. This has been achieved and details can be found in the thread Intercepting a Core Directive in Angular thanks to some help from Dave Woestenborghs.
There is, however, one current piece of the puzzle yet to solve and that is removing the ability to "Save" a member for users not in a given user group.
I know I can probably cancel the save event but I'm the sort of person that would prefer prevention rather than cure and so I'd rather not present users with a feature that is of no use or concern to them.
Should I be looking at an angular interceptor again to achieve this? It still seems a little hacky.
Hi Simon,
I never tried it with members but I think it works similar as I got it working for content
This removes the 'Save' and 'Save & Publish' button from a doctype with a specific content type. In the examples from the other thread you can figure out on how to check on user group.
An overview of all the action letters can be found here : https://our.umbraco.com/documentation/Extending/Section-Trees/tree-actions
Dave
Content is not the same as a Member. So you have to return a MemberDisplay
But then i can't find really a option to disable it maybe you have some ideas @dave
Thanks Dave but as Julien pointed out, unfortunately, the MemberDisplay class does not have an AllowedActions property like ContentItemDisplay
Ouch..should have checked that...
Just had this example lying around. Don't see it can be done using c# code.
So the only option is to use a interceptor.
Maybe it's possible without actually replacing the entire view. Here nathan woulfe has an example on how to change the html using a interceptor without swapping a view
https://github.com/nathanwoulfe/NestingContently/blob/master/src/NestingContently/backoffice/interceptor.js
Dave
Interesting, I will take a look into that. Thanks
Morning David, I've made good progress on this now and have it working without the check on userGroup so thanks for your help. My angular skills are far from an expert and I am struggling to get access to userService so I can perform the userGroup check. What is the correct way to gain access to the service here?
If I do the following I get a circular dependency error:
Hi Simon,
Can you post your full code ?
Dave
Sure, here you go.
Hi Simon,
I just looked at my interceptor code in Nexu.
I don't use a factory. But I use the notifications service by adding when pushing the interceptor
https://github.com/dawoe/umbraco-nexu/blob/develop/Source/Our.Umbraco.Nexu.Core/Client/interceptor.js#L5
Maybe that's the issue.
But I also using another pattern i see to get my own service.
https://github.com/dawoe/umbraco-nexu/blob/develop/Source/Our.Umbraco.Nexu.Core/Client/interceptor.js#L45
Dave
Thanks Dave, I've made some further progress. I think I now understand why I was getting the circular dependency errors, see the link in my comments below. This is where I am at now:
I am now fighting a new issue but too tired to make any further meaningful progress tonight. The new errors I think might relate to the callbacks:
The error in my last message is a red herring and doesn't appear to be linked to this work. The only thing I am currently struggling to resolve now is that the
resp
is returned before the promise is resolved and therefore the button is still appearing in the page. I need a way to stop the function returning until the promise is resolved.You will probably need to 'defer' your promise untill the call to userService is resolved.
https://github.com/dawoe/umbraco-nexu/blob/develop/Source/Our.Umbraco.Nexu.Core/Client/interceptor.js#L48
Perfect, thanks Dave! Here is a working version now.
One final thing left for me to do and that is to remove the actions menu but I think I should be able to use the same technique for that.
Thanks again for all of your help!
Cheers, Simon
For completeness (for now) I am removing the menu as follows:
I'm not sure I like either approach to the problem, it feels a bit hacky. There should probably be more granular control over user permissions in the Members section so will consider creating an issue for it on the tracker.
is working on a reply...