Reputation: 637
first let me clarify 2 things :
ACF proposes to add 'disabled' or 'readonly' to the inputs elements that supports this attributes, but it's not quite sufficient :
not all acf fields can be customized with readonly or disabled (of course, some fields don't need this, but others could use it, like checkbox or range)
'disabled' don't do the job : if you submit a form with a checkbox acf field disabled, it will delete the data of the field, so the field was modifiable, and in a bad way. You can only add it to the following fields :
$field['disabled']=1;
: 'text', 'textarea', 'number', 'email', 'url', 'password', 'date_picker', 'time_picker', 'select'$field['disabled']=array(<elements to disable>)
: 'checkbox', 'radio''readonly' is available on even less fields, and don't removes the values from the post request, so if a page with the field is open and the field was modified elsewhere, if you submit this page because you made changes on something else, it will also override the changes of this 'readonly' field
So, I tried a solution : I hook when the fields are updated, and I check if it comes from the front, by looking if the $_POST['acf'] is set, but I don't know if it's reliable ?
function prevent_acf_field_from_updating($null, $value, $post_id, $field) {
/*
* if $_POST['acf'] is not set, the update is from server side, so I don't prevent it
* I suppose ?
*
*/
if (!isset($_POST['acf'])) {
return $null;
}
/*
* to make a field disabled, I use a class
*
*/
if (!str_contains($field['wrapper']['class'], 'read_only_acf')) {
return $null;
}
return false;
}
add_filter('acf/pre_update_value', 'prevent_acf_field_from_updating', 10, 4);
I also add inert
attribute to the wrapper with filter acf/field_wrapper_attributes
But I'm really not sure it's a good way to do it, and if checking the $_POST['acf'] is reliable ?
Upvotes: 0
Views: 1177
Reputation: 637
After more investigation, I found a way that I think is good, so I'll answer my own question (if you don't want to read all the reasoning, jump to the code at the end of the answer).
As CBroe pointed out in its comment, the acf fields are only rendered to the admin panel, not the front (in wordpress, 'front' does not mean what is accessed by web browser, but what is not admin and is accessed by web browser. Admin section is referred as backend).
So there is 2 places to prevent specific acf fields from being updated from admin :
acf/pre_update_value
, as I tried to do in my questionacf/pre_update_value
filter :to prevent update from this filter, we need a reliable way to detect whether the field update action was initiated in admin or in server-side. I can think of 3 ways to do that :
by checking if the $_POST['acf']
is set, as I proposed in my answer
by detecting if the action is in admin, it could be simply done with is_admin()
but the link posted by CBroe shows a better and more complete way : https://florianbrinkmann.com/en/wordpress-backend-request-3815/
by wrapping the server side calls in a function, e.g. my_plugin_acf_field_update()
, and then use the function debug_backtrace()
to check if the call comes from this function
update_field()
in the wrapper function, which can be a painful depending of the state and structure of the project (in my case this is just fine, and I almost used that)Another way to prevent the update, is to prevent the fields from being submited in the first place, in the html.
To do that, html have the disabled
attribute for forms elements, and acf propose a way to add them to some of the fields, but it does not work well :
for some fields, it seems to work : ['text', 'textarea', 'number', 'email', 'url', 'password', 'date_picker', 'time_picker', 'select']
for some other fields it does not work : ['checkbox', 'radio']
. Why ? actually because acf adds a hidden field with the field ref, like that : <input type="hidden" name="acf[field_6617daff359b2]">
, so if a checkboxe is not submited because it's disabled, then acf receives the hidden input anyway, and then checks for any of the checkbox values, find none and deduce they are all unchecked, so it updates the field accordingly
and finally, for all other fields, acf does not propose any way to disable them, even if they are made of input elements, like the range field.
But there is another way, and this is the one I will use in my project : you can use the action hook that acf uses internally to render each field, and hook it before and after acf does, to add a fieldset
element with the disabled
attribute -> it will disable any element inside of it, including the hidden field. This should work and be robust, I hope.
This is how i do it :
/*
* there is one action hook suitable to open and close the fieldset element :
* 'acf/render_field' -> line 807 in advanced-custom-fields/includes/acf-field-functions.php
*
* acf uses priority 9 for its 'acf/render_field' hook -> see line 64 in advanced-custom-fields/includes/fields/class-acf-field.php
* so we hook before and after
*
* side note : we use a class, 'read_only_acf', to detect if a field should be disabled
*
*/
function open_fieldset_acf_disabled($field) {
if (str_contains($field['wrapper']['class'], 'read_only_acf')) {
echo "<fieldset disabled class='acf_disabled_fieldset' style='border:none; margin:0px; padding:0px;'>\n";
}
}
function close_fieldset_acf_disabled($field) {
if (str_contains($field['wrapper']['class'], 'read_only_acf')) {
echo "</fieldset>\n";
}
}
add_action('acf/render_field', 'open_fieldset_acf_disabled', 8);
add_action('acf/render_field', 'close_fieldset_acf_disabled', 10);
Upvotes: 2