Make field readonly for certain user group

I have a key field inside my collection. This field should only be editable by the admin. Any other user group should be able to view the value, but not edit it. Is this possible?

What I tried:
Inside the Permission settings of the collection, I enabled UPDATE and added this php code:

<?php

if ($context->user && $context->user['group'] != 'admin')
    $context->options['fields']['key'] = false;

But it doesn’t seem to be working. A non-admin user can still edit and update the key field.

Thanks for your support!

There are 2 parts to your request:

(1) Preventing the form field to be editable

Afaik there is currently no direct option you could utilize to change the collections form behavior.
You’re facing the same question as stated here Extend "view" class of Collections module

What you want is to extend the view and maybe append a JS snipped that turns your readonly-field into readonly for non-admins.

There is an event triggered when a template is rendered. You can hook into that event and extend the to-be-rendered-template-source.

Something along those lines

// config/bootstrap.php
<?php

$this->on('collections.entry.aside', function($collectionName){
  if(collectionName != 'YOUR_COLLECTION_NAME') return;
  
  echo '<script>document.querySelector('input[name="YOURFIELDNAME"]').readOnly = true;</script>';  

})

(2) Preventing values to be saved to the DB

For preventing a value to be set I’d say you’re on the right track.

In the UPDATE rule scripts $context variable you also have the key entry.
If I am not mistaken, you could screen for “is admin” and if not, then remove the admin-only-keys from the entry object.

<?php

if ($context->user && $context->user['group'] != 'admin'){
    unset($context->entry['key']);
}

This should prevent anybody but admins to “save” a value for that field.

1 Like

Great, thanks a lot for your help!

I had trouble setting up (1) so I ended up using (2), which is totally fine for now.

The problem with (1) is that I couldn’t access the key input-field. Somehow Cockpit is not applying any id or name to distinguish it by.

For (1) I see a bind attribute that references the fields name:

<input ref="input" class="uk-width-1-1" bind="entry.field1" type="text">

You could go for that instead of the input name attribute.

// config/bootstrap.php
<?php

$this->on('collections.entry.aside', function ($collectionName) {
    if (collectionName != 'YOUR_COLLECTION_NAME') return;

    $user = $this->module('cockpit')->getUser();
    
    if (!$user || $user['group'] != 'admin') {
        // IE11 not supported because `?.`
        echo "document.querySelector('input[bind=\"entry.key\"]')?.readOnly = true;";
    }
});
    


1 Like

The idea is good. Unfortunately, in my case I cannot access any relevant input fields at all. Even using document.querySelectorAll('input') in the function only returns 3 input fields from another level (i.e. the search field on top), but none of the form.

Maybe you have another idea? Thanks again for the help!

I’m new to Cockpit and haven’t dived at all into how the Javascript lib Riot is used in the whole rendering process of the templates.

I’m suspecting this is “just” a little race-condition, where the form is not yet initialized while the template in general is mostly rendered.

So - easy hacknfix is to delay the script.

setTimeout(function(){
  document.querySelector('input[bind="entry.key"]')?.readOnly = true;
}, 100); // 100ms delay might be enough

The clean solution would be to “wait” until the element is present (with a setInterval and an additional setTimeout for a timeout) - but that might be just overkill and the little delay hack might do “just fine”.

Nice, that was exactly what was missing to make it work. Here is my final code inside bootstrap.php:

        // ...

        // Disable key field:
        $app->on('collections.entry.aside', function ($collectionName) {
          if ($collectionName != 'translations') return;
          $user = $this->module('cockpit')->getUser();
          if (!$user || $user['group'] != 'admin') {
            echo "<script>
                  setTimeout(function() {
                    var inputField = document.querySelector('input[bind=\"entry.key\"]')
                    if (inputField) {
                      inputField.readOnly = true
                    }
                  }, 100);
                 </script>";
          }
        });

Thanks again! Glad it is working now!