Drupal 7 Read-Only Fields

Posted on 2013-08-14 14:00:00+00:00

In an earlier version I showed you how to make CCK fields read-only using Drupal 6. CCK was merged into Drupal 7 as the fields API, so a new approach is required. This new post will show an easy and safe technique to make Drupal 7 fields read-only.

Generally, using form alter on any form field and setting #disabled => TRUE works just fine. However, form alter is too early of a hook to use on entity fields. Moreover, if you disable a field, the browser won't even post the value. This can result in annoying validation issues.

Learning Drupal? Subscribe to my Drupal articles, tips and tutorials.

The correct and safe way to do lock changes to a widget is to mark its HTML elements as read only, not as disabled. To do this, we use the readonly attribute. This ensures that the browser posts the value, unlike disabled. But we can't just stop here. We need to make sure the value isn't changed by a crafty user modifying his or her post request. Just imagine if they user the Chrome Inspector and altered the HTML attributes directly: they'd be able to modify the field. We need to prevent this.

In case you don't already have a field you want to lock down, let's create one really quickly.

  1. Browse to admin/structure/types.
  2. Find an existing content type, and click on manage fields.
  3. Add a field and give it the label Locked. The machine name should appear automatically as field_locked. Be sure to choose a text field type, using a textfield widget.

Now, let's hook in to the node form so we can add our #after_build callback. Keep in mind that in this example, we're adding our callback to all node forms. You might want to further limit this to a specific content type. You might also want to check if this is an existing node, as locking the field on node creation might not be necessary.

<?php

/**                                                                           
 * Implements hook_form_alter().                                              
 */                                                                           
function example_form_alter(&$form, &$form_state, $form_id) {                 
  if (isset($form['#node']) && $form_id == $form['#node']->type .'_node_form') {
    $form['#after_build'][] = 'example_after_build';                          
  }                                                                           
}

Let's keep things simple and modify a text widget. For simple widgets, the fields API generally uses the key value.

<?php

function example_after_build($form, &$form_state) {                           
  $field = 'field_locked';
  $form[$field]['und'][0]['value']['#attributes']['readonly'] = 'readonly';
  $form_state['values'][$field]['und'][0]['value'] = $form[$field]['und'][0]['value']['#default_value'];
  return $form;
}

And that's it! You can now count on your field being read-only. It will still be posted by the browser. And even if a user tries to post a changed value - or uses the browser inspector to edit the contents of the fields - our build function restores the original value. Fast, safe, and secure.

Below is the complete code for reference.

<?php

/**                                                                           
 * Implements hook_form_alter().                                              
 */                                                                           
function example_form_alter(&$form, &$form_state, $form_id) {                 
  if (isset($form['#node']) && $form_id == $form['#node']->type .'_node_form') {
    $form['#after_build'][] = 'example_after_build';                          
  }                                                                           
}

function example_after_build($form, &$form_state) {                           
  $field = 'field_locked';
  $form[$field]['und'][0]['value']['#attributes']['readonly'] = 'readonly';
  $form_state['values'][$field]['und'][0]['value'] = $form[$field]['und'][0]['value']['#default_value'];
  return $form;
}

Comments

bmayjor2013-08-14 15:20:26+00:00

Nice, I was looking for a way to do this the other day. Thanks

Reply
darthsteven2013-08-14 16:54:50+00:00

Also, have a look at the Field extra widgets module: https://drupal.org/project/field_extrawidgets which provides a field widget which is read only, which might work for some use-cases.

Reply
Adam2013-08-23 18:36:22+00:00

I have used a drupal_add_js that uses jQuery to add the "readonly" attribute.

Reply
silviogutierrez2013-08-23 21:50:17+00:00

Adam, while this will work cosmetically, a user could just forge a POST request with different values, changing the field. Or using Firebug/Chrome Inspector, modify the values of the field. Moreover, he could just turn off JavaScript.

That means you also have to lock the backend too, which is done in the method I show above.

Reply
anrikun2013-12-28 14:20:42+00:00

You may try https://drupal.org/project/field_readonly

Reply
silviogutierrez2013-12-28 15:01:40+00:00

Very nice. Does this module make use of the above technique, or another approach?

Reply
blainelang2013-08-14 23:57:48+00:00

I had looked at the field_extrawidgets module as it proposes to provide readonly widget but it offers no way to set the field value. I had a need for a readonly text field were I was setting the value in a form_alter anyways and I didn't like the way it appears in the browser when using the disabled attribute. Your solution works the way I wanted.

Thanks again for posting it.

Reply
Thomas Svenson2013-08-15 16:11:26+00:00

Very nice info. Just wondering, have you looked at the Field Permissions, https://drupal.org/project/field_permissions, module?

With it you can get read only with the "View any value for [field]" permission on a role basis and then easily give other roles edit rights.

Reply
densolis2014-04-10 04:47:39+00:00

I did the above for a text box and it worked great. However, I also need to so this for a check box field and a select list field for a taxonomy term and the above coded did not work. Any suggestions?

Using dsm, I was able to display the $form array and the appropriate array position ( $form[$field]['und'][0]['value']['#attributes']['readonly'] = 'readonly';) was set properly, but I could still change the values in the check box and select list taxonomy term fields.

Any suggestions on what I did wrong?

thanks

Reply
insen2014-04-18 23:32:52+00:00

Ok, finally found the solution. You have to do it through #required value. If you have trouble after reading this http://drupal.stackexchange.com/questions/97011/how-can-i-skip-field-validation-if-the-field-is-hidden

I can provide more information by myself.

Reply
insen2014-04-18 15:06:05+00:00

I got the same problem. However setting 'readonly' value doesn't work in this case because select lists/check boxes already are 'readonly' (you can't change values, you just pick one). The way I'm trying to pass validation issues with select list is to set it as DISABLE. Then pass it with hidden field as sugested here: http://drupal.stackexchange.com/questions/95320/how-to-identify-disabled-fields-in-form-submit-handler

I will definitely reply if there is any success.

Reply
insensaty2014-04-19 13:13:33+00:00

http://drupal.stackexchange.com/questions/97011/how-can-i-skip-field-validation-if-the-field-is-hidden

Finally I have figured out how to solve it. Check out the link. You have to do it through #required value in #after_build. If you are still expierincing troubles let me know. I can show you my code ;)

Reply
Benjamin Israel2014-09-11 20:29:17+00:00

I could not make this code work on my website for some reasons, although it is perfectly working on a brand new D7 test installation. The readonly feature was implemented but you could still override it AND save the new value using Chrome inspector.

But for the record I have achieved the same result using rules to save again the previous value of a specific field every time a node is updated.

Reply
Matthew Honer2015-09-25 07:26:45+00:00

Using: Drupal 7.39

I was hijacking basic_cart form and trying to make the fields readonly, after much stuffing around.

I needed the fields to be submitted but disallow the user to edit the contents of those fields.

I tried your method but it didn't work, I was able to disable the field using:

$form[$fieldName][LANGUAGE_NONE][0]['#disabled'] = TRUE;

but that meant it (the field) didn't get submitted, so after looking into includes/form.inc I found this gem (see pic)

So then I found this worked.

$form[$fieldName][LANGUAGE_NONE][0]['#disabled'] = TRUE; $form[$fieldName][LANGUAGE_NONE][0]['#allow_focus'] = TRUE;

You may or may not need to apply this patch ( I think it works without) https://www.drupal.org/files/issues/drupal-7.x-add_readonly_to_form_fields-3.patch

And thats how I disabled my fields :D

Reply

Post New Comment