Jump to content
ethanbeyer

Custom User Types, Page Edit vs. Profile Edit

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

Share this post


Link to post
Share on other sites

@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.

 

Share this post


Link to post
Share on other sites

@Zekathanks for pointing me there. I wasn't even aware that this issue exists. Otherwise I wouldn't have created my issue regarding tabs not being displayed correctly.

 

  • Like 1

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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.

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Kiwi Chris
      I have a role that has page edit, view, and clone permissions on a specific template.
      If a page using the template is locked by a user in a role with lock/unlock permissions on the template , the only button alongside it in the page tree is view, for users who don't have lock/unlock permissions.
      If however, I also give the role page-lock permission on the template, they then get additional buttons, edit, copy, and unlock.
      I don't actually want to give this role unlock permissions, but I do want the copy (clone) button to display alongside the page in the page tree.
      Elsewhere, I've discussed how I've worked out how to create a hook to unlock the copy, but I want to keep the original page so a user without lock permissions can't unlock from the page tree it to make changes.
      Question: What method should I hook into to intercept any attempt to change the lock status?
    • By Robin S
      Repeater Images
      Adds options to modify Repeater fields to make them convenient for "page-per-image" usage. Using a page-per-image approach allows for additional fields to be associated with each image, to record things such as photographer, date, license, links, etc.
      When Repeater Images is enabled for a Repeater field the module changes the appearance of the Repeater inputfield to be similar (but not identical) to an Images field. The collapsed view shows a thumbnail for each Repeater item, and items can be expanded for field editing.
      Screencast

      Installation
      Install the Repeater Images module.
      Setup
      Create an image field to use in the Repeater field. Recommended settings for the image field are "Maximum files allowed" set to 1 and "Formatted value" set to "Single item (null if empty)". Create a Repeater field. Add the image field to the Repeater. If you want additional fields in the Repeater create and add these also. Repeater Images configuration
      Tick the "Activate Repeater Images for this Repeater field" checkbox. In the "Image field within Repeater" dropdown select the single image field. You must save the Repeater field settings to see any newly added Image fields in the dropdown. Adjust the image thumbnail height if you want (unlike the core Images field there is no slider to change thumbnail height within Page Edit). Note: the depth option for Repeater fields is not compatible with the Repeater Images module.
      Image uploads feature
      There is a checkbox to activate image uploads. This feature allows users to quickly and easily add images to the Repeater Images field by uploading them to an adjacent "upload" field.
      To use this feature you must add the image field selected in the Repeater Images config to the template of the page containing the Repeater Images field - immediately above or below the Repeater Images field would be a good position.
      It's recommended to set the label for this field in template context to "Upload images" or similar, and set the visibility of the field to "Closed" so that it takes up less room when it's not being used. Note that when you drag images to a closed Images field it will automatically open. You don't need to worry about the "Maximum files allowed" setting because the Repeater Images module overrides this for the upload field.
      New Repeater items will be created from the images uploaded to the upload field when the page is saved. The user can add descriptions and tags to the images while they are still in the upload field and these will be retained in the Repeater items. Images are automatically deleted from the upload field when the page is saved.
      Tips
      The "Use accordion mode?" option in the Repeater field settings is useful for keeping the inputfield compact, with only one image item open for editing at a time. The "Repeater item labels" setting determines what is shown in the thumbnail overlay on hover. Example for an image field named "image": {image.basename} ({image.width}x{image.height})  
      https://github.com/Toutouwai/RepeaterImages
      https://modules.processwire.com/modules/repeater-images/
    • By Orkun
      Hi Guys
      I have a problem with a repeater field. Some weeks ago I had exported a repeater field from a dev installation and then imported it to a live installation. All just had worked fine as I remember (the field was created etc...). But today I got an email from the developer of our customer that he has a problem with two repeater fields. When he had changed the fields of another repeater it has also changed it in the other repeater field (which I had imported some weeks ago). I investigated this further and found out that exporting/importing of repeater fields doesn't work for my version of processwire (we are using 2.7.3) (see https://github.com/processwire/processwire-issues/issues/416). 
      So I decided to remove the repeater field and create it again manually on the live enviroment. I could remove the repeater field from the template but I can't delete the repeater field because it is referring to a false repeater template. How can I fix this issue so that I can delete the repeater field?
       
      The "event_dates" repeater field is referring to the "repeater_speciality_adresses" template which is wrong.

      When I try to delete the field I get this error:

       
      Do I need to manually remove the field from the db? What do I exactly need to do?
      Kind Regards
      Orkun
    • By pwFoo
      Hi,
      I try to add page-edit-own and page-delete-own permissions, but it's strange...
      If a add the custom permissions it looks like both are children of page-edit respectively page-delete. I played with added / revoked permissions, but I can't get it work, that a user of a role just can delete own content.
      First the user can't delete any content and now the user can delete own and foreign pages 🤪
      Is there a tutorial to learn more about the PW permissions?
      Or do I have to rename the permissions to page-own-edit and page-own-delete to be independent from page-edit and page-delete?
    • By David Karich
      ProcessWire InputfieldRepeaterMatrixDuplicate
      Thanks to the great ProModule "RepeaterMatrix" I have the possibility to create complex repeater items. With it I have created a quite powerful page builder. Many different content modules, with many more possible design options. The RepeaterMatrix module supports the cloning of items, but only within the same page. Now I often have the case that very design-intensive pages and items are created. If you want to use a content module on a different page (e.g. in the same design), you have to rebuild each item manually every time.
      This module extends the commercial ProModule "RepeaterMatrix" by the function to duplicate repeater items from one page to another page. The condition is that the target field is the same matrix field from which the item is duplicated. This module is currently understood as proof of concept. There are a few limitations that need to be considered. The intention of the module is that this functionality is integrated into the core of RepeaterMatrix and does not require an extra module.
      Check out the screencast
      What the module can do
      Duplicate a repeater item from one page to another No matter how complex the item is Full support for file and image fields Multilingual support Support of Min and Max settings Live synchronization of clipboard between multiple browser tabs. Copy an item and simply switch the browser tab to the target page and you will immediately see the past button Support of multiple RepeaterMatrix fields on one page Configurable which roles and fields are excluded Duplicated items are automatically pasted to the end of the target field and set to hidden status so that changes are not directly published Automatic clipboard update when other items are picked Automatically removes old clipboard data if it is not pasted within 6 hours Delete clipboard itself by clicking the selected item again Benefit: unbelievably fast workflow and content replication What the module can't do
      Before an item can be duplicated in its current version, the source page must be saved. This means that if you make changes to an item and copy this, the old saved state will be duplicated Dynamic loading is currently not possible. Means no AJAX. When pasting, the target page is saved completely No support for nested repeater items. Currently only first level items can be duplicated. Means a repeater field in a repeater field cannot be duplicated. Workaround: simply duplicate the parent item Dynamic reloading and adding of repeater items cannot be registered. Several interfaces and events from the core are missing. The initialization occurs only once after the page load event Changelog
      1.0.4
      Bug fix: Various bug fixes and improvements in live synchronization Bug fix: Items are no longer inserted when the normal save button is clicked. Only when the past button is explicitly clicked Feature: Support of multiple repeater fields in one page Feature: Support of repeater Min/Max settings Feature: Configurable roles and fields Enhancement: Improved clipboard management Enhancement: Documentation improvement Enhancement: Corrected few typos #1 1.0.3
      Feature: Live synchronization Enhancement: Load the module only in the backend Enhancement: Documentation improvement 1.0.2
      Bug fix: Various bug fixes and improvements in JS functions Enhancement: Documentation improvement Enhancement: Corrected few typos 1.0.1
      Bug fix: Various bug fixes and improvements in the duplication process 1.0.0
      Initial release Support this module
      If this module is useful for you, I am very thankful for your small donation: Donate 5,- Euro (via PayPal – or an amount of your choice. Thank you!)
      Download this module
      > Github: https://github.com/FlipZoomMedia/InputfieldRepeaterMatrixDuplicate
      > PW module directory: – soon –
×
×
  • Create New...