bernhard Posted April 26, 2017 Share Posted April 26, 2017 hi everybody, i developed an intranet application for a client. now we want to grant access to some more employees that get the "employee" role. employees should be able to see all the data (meaning they have to have edit access to see all the input forms holding data for all the fields) but not EDIT this data. which approach would you go? i see this options change all field access settings to make fields non-editable by this role but visible in page editor (would work, but i want to save me from that work) set field access settings via api (how?) hook Field::editable --> does not work. i guess it is only called when field->useRoles is true. Don't know how to hook correctly here. prevent only the page save for this pages totally different approach? thanks for your help! 1 Link to comment Share on other sites More sharing options...
tpr Posted April 26, 2017 Share Posted April 26, 2017 I would probably use 1, or go with 2 if I would need some challenge Option 4 sounds fun, but also with a Js/CSS extra to make the inputs "disabled". 1 Link to comment Share on other sites More sharing options...
bernhard Posted April 26, 2017 Author Share Posted April 26, 2017 thank you for the quick reply. i tried a lot yesterday without progress. i just had idea 4 while writing the post and it was really easy: $this->addHookBefore('Pages::save', function($event) { $user = $this->wire->user; // checks depending on the page that was saved? // $page = $event->arguments(0); // early return if user is allowed to save in general if($user->isSuperuser()) return; if($user->hasRole('senior')) return; if($user->hasRole('marketing')) return; if($user->hasRole('office-management')) return; // throw error if user is not allowed to save throw new WireException('You are not allowed to edit this page!'); }); this works quite well. of course with the drawback that all save and add-new buttons are visible (also on pagetable fields and some other spots). but i think that's the quickest and cleanest solution for my setup... but still i would be interested how to hook the editable state of a field. so i would still appreciate some advice here Link to comment Share on other sites More sharing options...
abdus Posted April 26, 2017 Share Posted April 26, 2017 1 hour ago, bernhard said: but still i would be interested how to hook the editable state of a field. so i would still appreciate some advice here You can hook into ProcessPageEdit::buildForm and modify its arguments. You can add readonly attribute to all text fields (disabled works too), remove submit buttons and disable uploads. wire()->addHook('ProcessPageEdit::buildForm', function (HookEvent $e) { $editorRoles = ['senior', 'marketing', 'office-management']; foreach ($editorRoles as $role) { if (wire()->user->hasRole($role)) return; } // set all text fields readonly $form = $e->arguments(0); foreach ($form->find("type%=text") as $ta) { $ta->attr('readonly', true); } // remove submit buttons foreach ($form->find("type%=submit") as $button) { $form->remove($button); } // disable uploads foreach ($form->find("name%=image|file") as $uploadable) { $uploadable->noUpload = true; } }); But removing submit buttons leaves dropdown items behind which I think is added with JS, so you may need use JS to remove it. (I'm using AdminThemeUIKit, other themes might be different) [].forEach.call(document.querySelectorAll('.pw-button-dropdown'), dd => dd.parentNode.removeChild(dd)); I'm not sure about other fields, though. 3 Link to comment Share on other sites More sharing options...
kixe Posted April 26, 2017 Share Posted April 26, 2017 To change settings for all fields via API you could use: /** * make all fields accessible to view only for all roles except superuser * */ foreach ($fields as $field) { if ($field->hasFlag(8) && $field->name !== 'title') continue; // skip system fields `Field::flagSystem` $field->addFlag(160); // Add flags `Field::flagAccess` = 32 + `Field::flagAccessEditor` = 128 // Applicable only if the `Field::flagAccess` is set to this field's flags. $field->editRoles = array(); // leave empty if you want to give access to superuser only $field->viewRoles = array(1026); // assign field view permission to specific roles (IDs) // save the field $field->save(); } 5 Link to comment Share on other sites More sharing options...
thetuningspoon Posted February 6, 2021 Share Posted February 6, 2021 I figured out a way to do this ( @bernhard's original request, solution #3), but it wasn't easy. I added a page-edit-edit-fields permission which can be added or revoked per-template, and without which none of the fields are editable in the page editor. It required 3 hooks to make the permission work. /site/ready.php /** * Implements a custom page-edit-edit-fields permission. By default, all fields will be view only when the page-edit permission is granted. Adding the page-edit-edit-fields permission makes all fields on the page editable. * This could be accomplished by implementing access settings on every field of the template, but this makes it much easier. * * Individual field access settings, if enabled, will override this feature. * * Both this hook and the one below are required to make this work. */ $this->wire()->addHookAfter('Field::editable', 'fieldEditableHook'); function fieldEditableHook(HookEvent $event) { $return = $event->return; $page = $event->arguments(0); $user = $event->arguments(1); $field = $event->object; if($return === true && $page !== null) { // No need to check our page-edit-edit-fields permission if the field is already not editable or we're not checking page context if(!$user) $user = $event->wire('user'); // If no user passed in, use the current user // If the current user does not have page-edit-edit-fields permission, disable edit if(!$user->hasPermission('page-edit-edit-fields', $page)) { $return = false; } } $event->return = $return; } $this->wire()->addHookAfter('Field::viewable', 'fieldViewableHook'); function fieldViewableHook(HookEvent $event) { $return = $event->return; $page = $event->arguments(0); $user = $event->arguments(1); $field = $event->object; if($return === false && $page !== null) { // No need to check our page-edit-edit-fields permission if the field is already viewable if(!$user) $user = $event->wire('user'); // If no user passed in, use the current user // If the current user does not have page-edit-edit-fields permission, enable viewable permission if(!$user->hasPermission('page-edit-edit-fields', $page)) { $return = true; } } $event->return = $return; } $this->wire()->addHookBefore('Field::getInputfield', function(HookEvent $event) { // Exclude any field types we don't want to make uneditable by default $excludedFieldtypes = [ 'FieldtypeLeafletMapMarker', ]; $field = $event->object; $page = $event->arguments(0); $user = $event->wire('user'); if(in_array($field->type, $excludedFieldtypes)) return; // If the current user does not have page-edit-edit-fields permission, disable edit regardless of the field's roles if(!$field->useRoles && !$user->hasPermission('page-edit-edit-fields', $page)) { $field->set('useRoles', true); //$field->addFlag(Field::flagAccess); // May not be necessary //$field->addFlag(Field::flagAccessAPI); // May not be necessary $field->addFlag(Field::flagAccessEditor); } }); 1 Link to comment Share on other sites More sharing options...
thetuningspoon Posted February 13, 2021 Share Posted February 13, 2021 Here is a cleaner and more thoroughly tested version, which eliminates some of the code duplication. The Field::viewable hook is no longer needed. I'm not sure why the Field::editable is still necessary, but it doesn't work without it. /site/ready.php /** * Implements a custom page-edit-edit-fields permission. By default, all fields will be view only when the page-edit permission is granted. Adding the page-edit-edit-fields permission makes all fields on the page editable. * This could be accomplished by implementing access settings on every field of the template, but this makes it much easier. * * Individual field access settings, if enabled, will override this feature. */ $this->wire()->addHookBefore('Field::getInputfield', function(HookEvent $event) { $page = $event->arguments(0); if($page instanceof User) return; // Exclude any field types we don't want to make uneditable by default $excludedFieldtypes = [ 'FieldtypeFieldsetClose', 'FieldtypeFieldsetOpen', 'FieldtypeFieldsetTabOpen', 'FieldtypeFieldsetPage', 'FieldtypeFieldsetGroup', ]; $field = $event->object; $context = $event->arguments(1); $user = $event->wire('user'); if($context || in_array($field->type, $excludedFieldtypes)) return; // If the current user does not have page-edit-edit-fields permission, disable edit regardless of the field's roles if(!$field->useRoles && !$user->hasPermission('page-edit-edit-fields', $page)) { $field->setRoles('view', [$event->wire('config')->guestUserRolePageID]); // Set all roles to have view permission by passing in the guest role $field->setRoles('edit', []); $field->addFlag(Field::flagAccessAPI); $field->addFlag(Field::flagAccessEditor); $field->set('useRoles', true); $field->set('disabledByHook', true); } }); /** * Only the above Field::getInputfield hook should be required since it sets $field->setRoles('edit', []); but for some reason the main content tab will still appear editable unless we add this hook */ $this->wire()->addHookAfter('Field::editable', function() { $return = $event->return; $field = $event->object; // If the current was field disabled by our other hook then reflect that here as well if($field->disabledByHook) { $return = false; } $event->return = $return; }); Link to comment Share on other sites More sharing options...
Robin S Posted February 13, 2021 Share Posted February 13, 2021 Another way? // Make all field inputfields non-editable in Page Edit $wire->addHookBefore('ProcessPageEdit::execute', function(HookEvent $event) { if(!$event->wire()->user->hasRole('read-only')) return; $event->wire()->addHookAfter('Field::getInputfield', function(HookEvent $event) { $event->return->editable(false); }); }); // Remove the Settings tab $wire->addHookAfter('ProcessPageEdit::buildForm', function(HookEvent $event) { if(!$event->wire()->user->hasRole('read-only')) return; $form = $event->return; $tab = $form->find('id=ProcessPageEditSettings')->first(); $form->remove($tab); $event->object->removeTab('ProcessPageEditSettings'); }); 1 Link to comment Share on other sites More sharing options...
thetuningspoon Posted February 13, 2021 Share Posted February 13, 2021 @Robin S That looks nice and clean. Would this actually make it appear non-editable, or just prevent the values from being saved? Link to comment Share on other sites More sharing options...
Robin S Posted February 14, 2021 Share Posted February 14, 2021 1 hour ago, thetuningspoon said: Would this actually make it appear non-editable, or just prevent the values from being saved? Both. It causes the inputfield to be rendered via renderValue() so there's no input, and even if somebody manually added an input in their browser any processing of the inputfield is prevented. 3 Link to comment Share on other sites More sharing options...
bernhard Posted February 14, 2021 Author Share Posted February 14, 2021 Should have known that 5 years ago ? Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now