Jump to content

Custom User Types, Page Edit vs. Profile Edit


ethanbeyer
 Share

Recommended Posts

Hello,

I recently posted in this topic, but I decided to start my own thread because while I believe my issue is related to the one in that thread, they are not exactly the same:

 

I have created a custom User Template in the method outlined in the docs. I am creating a directory, so it made sense that every page in the directory was a Directory Member, so they could log in and edit their own information while also keeping the entire directory protected behind a login wall.

So the new user type is created: "directory-member".

I then created two new roles: "member" and "directory-admin":

  • The "member" only has the ability to View directory-member pages, and "profile-edit", which allows them to manage their own information.
  • The "directory-admin" has the ability to edit any directory-member pages, and administer users.

Some Directory Members are both, but all have at least the "member" role.

The first hint that something was wrong was when I was testing a "member" user and I could not add a new item to a repeater on that profile. The url for the profile edit (this will be important shortly) is site.dev/admin/profile. The repeater is set up to load new items through AJAX. If this option is turned off, the rest of this issue is no longer completely valid. But as I have found what I believe to be a pretty large issue in the Processwire codebase, I thought it worth bringing up.

See, every page (even a user) has a $page->editUrl() method, and it returns a URL like this: site.dev/admin/access/users/edit/?id=2096. That's all good and fine for users that have page-edit permissions, but if they don't, that link will resolve to the admin's equivalent of a 404.

So the way that Processwire currently gets around this is by creating a specific editing area for a user to interact with only their profile: /admin/profile. And that works pretty nicely, except for the fact that nowhere is editUrl() ever made aware of the difference. editUrl() is not hookable, and whether or not a page is editable is based on the PagePermissions module.

On top of that, there are several core modules that hardcode a search-and-replace (see InputfieldRepeater.module:627) where the editing screen is for Users. This doesn't allow for a huge degree of flexibility that is offered in other places throughout Processwire. If line 627 of InputfieldRepeater is changed from this:

$editorUrl = str_replace('/access/users/edit/', '/page/edit/', $editorUrl);

to this:

$editorUrl = str_replace('/access/users/edit/', '/profile/', $editorUrl);

...the AJAX repeaters work. It's maddening!

As is brought up in the thread I attached above, a lot of the features of page editing are missing within /admin/profile/, and it just makes for an altogether strange editing experience. A user who has "page-edit" permissions for templates other than directory-member, but does have "profile-edit" permissions, will see their user in the Page List, but cannot edit their Page unless they hover over the wrench and click the "Profile" link. It just seems - off.

I think what this hinges on for me is that the editUrl() of the user should be "/admin/profile/" if that user is logged in (and their page should be editable from the Page List), or the "/admin/access/users/edit/" url; regardless of the URL, both links should resolve to the Page Edit screen, as the Profile Edit screen seems to be a unnecessarily neutered version of Page Edit.

  • Like 5
Link to comment
Share on other sites

  • 2 weeks later...

@ethanbeyer Thank you for bringing this up. I also have the feeling that the profile edit page functionality can be improved.

I recreated your example setup on the latest dev branch.

Same behaviour here for repeater fields on the profile edit page of the member user when they only have profile-edit permissions.
If you add page edit permissions to the member role and then go to the user template Access settings and add view and edit permissions for the member role, then the repeater field is working.

But this is not a good solution. Because once you enable page-edit permissions for the member role, they get access to the page tree which might not be desired. And you'd have to go through all your templates' access settings to make sure that the member user can only do what you want to allow them to do which is a bit tedious.
Also it is not obvious that you need to give page edit permissions for the user template to a role that already has profile edit permissions. Permission to edit the profile should imply the permission to edit the user template.

All in all this is very confusing behaviour.

I'd consider this a bug. Don't know if others would agree and would be happy to hear their opinion on this.

 

Link to comment
Share on other sites

@ethanbeyer Did you manage to work around the profile edit screen limitations?

I have a directory site where users are the directory pages. Their profiles have lots of fields, including tabs andf repeaters.
Since it was not possible to use the native profile edit process, I needed to find a workaround.
I have a custom Dashboard page in the admin where users have a link to their user page edit screen URL like /access/users/edit/?id=1377
To have this profileEditUrl easily available for every user, I added it as a custom property via a hook:

$this->addHookProperty('User(template=member)::profileEditUrl', $this, 'addPropertyProfileEditUrl');
	/**
	 * adds profile edit URL property to every member user 
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function addPropertyProfileEditUrl($event) {

		$user = $event->object;
		$event->return = $this->pages->get('template=admin, name=users')->url . "edit/?id={$user->id}"; 

	}

I had to give the member role page edit permissions for the member template. So I needed a way to make sure that they could not access other members' edit urls by just changing the id parameter in the URL.
My hook for that looks like

/**
  *  prevent member user from editing other member pages
  */
$this->addHookAfter('ProcessPageEdit::loadPage', function(HookEvent $event) {

  $id = $event->arguments[0];
  $user = $this->user;
  if($user->isSuperuser()) return;
  $editPage = $this->wire('pages')->get((int) $id);
  if($editPage->template != 'member') return;
  
  // normal members can only edit their own profile
  if($editPage->id != $user->id) wire('session')->redirect($user->profileEditUrl);


});

Could also have hooked into Page::editable and apply logic there, I guess...

  • Like 3
Link to comment
Share on other sites

@gebeer It looks like you got further into the solution than I did. Tabs and repeaters were a problem for the Profile Edit screen for me, too. So I eventually gave up and gave users of that template type a role that gave them page-edit permissions, but I hadn't worked out how to make sure that people were only editing their own profiles - so I think your two hooks are helpful there!

I know there's a difference in form and function for Profile Edit and Page Edit, but our scenario (where Users = Content)  seems like a time when keeping the two completely separate breaks down. I think your method is clean and I will give implementation a shot. Thank you!!

 

Edit: Just saw you'd posted twice in this thread, and just now read the first:

Quote

Because once you enable page-edit permissions for the member role, they get access to the page tree which might not be desired.

That. Exactly that.

9 hours ago, gebeer said:

Also it is not obvious that you need to give page edit permissions for the user template to a role that already has profile edit permissions. Permission to edit the profile should imply the permission to edit the user template.

In full! If it's on a profile page, and I've given the user the permission to edit their page...they should be able to do whatever is necessary on it.

  • Like 2
Link to comment
Share on other sites

@gebeer I've looked at your solution and put it in place - and it s a much better start than I would've thought of. I'm not sure it does everything I need it to right now, but I can keep tinkering and I will report back.

This is the first time when something ProcessWire does hasn't made a lick of sense to me.

Link to comment
Share on other sites

@ethanbeyer As I mentioned above, I have a custom dashboard process module with links to the areas that members are allowed to view/edit. One is the edit page for their profile at /access/users/edit/?id=xxxx.
I then redirect them to the dashboard upon login through a hook so they never see the page tree.

$this->addHookBefore('ProcessHome::execute', $this, 'rootPage');
	/**
	 * Redirect users with custom role to another page after login
	 */
	public function rootPage(HookEvent $event) {

		if($this->config->ajax) return; // this is needed so that ajax background tasks still work
		$user = $this->user;
		if($user->isSuperuser() || !$user->hasRole('member')) return;
		if($user->hasRole('member')) {
			$url = $user->statusPageUrl; // custom property added via hook that returns the dashboard URL
		}
		$this->session->redirect($url);		
	}

I also have a hook that prevents them from viewing the page tree (and other pages, if required).

 

    $this->addHookAfter('Page::viewable', $this, 'viewable');
	/**
	 * Give only superusers access to certain pages
	 */
	public function viewable(HookEvent $event) {
		
		if($this->config->ajax) return; // this is needed so that ajax background tasks still work
		$page = $event->object;
		if($this->user->isSuperuser() || $page->template != 'admin') return;
		$denyPages = array(3); // 3=page list
		if (in_array($page->id, $denyPages)) {
			$this->session->redirect($this->user->statusPageUrl);
		}
	}

I then deactivated Breadcrumbs for the member users so they don't see what else would be available.

The whole logic sits on top of a custom admin theme which is based on Reno theme. But I think this is not necessary at all. It just made it easier to remove the main navigation and other stuff that I didn't need.

With all that in place the members can now only see what they need to. And I can use the dashboard to render some summary information about their profile etc.

This was quite some work to setup but in the end it seemed more feasible to go that route rather than trying to change the core profile edit page functionalities.

But I still can't see the security benefits that come through the current restrictions in the profile edit page. If that page allows users to change their password why not allow them to change other information?

EDIT: Setting up a custom dashboard process module actually isn't that hard and can be fun @bernhard wrote a great tutorial about it.

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...