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.
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.
admin/structure/types
.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;
}
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.
I have used a drupal_add_js that uses jQuery to add the "readonly" attribute.
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.
Very nice. Does this module make use of the above technique, or another approach?
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.
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.
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
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.
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.
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 ;)
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.
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