Drupal - true "Save Draft" functionality for node forms by overriding node form validation

Let me clarify staright away that the code below has been tested successfully against Drupal 6 only. But I believe it should work with Drupal 7 also with minor changes (if any required). Okay now, let's come to the point.

"Save as Draft" as you can imagine is a pretty handy option for content authors. It allows them to save their unfinished work mid-way and come back to it later. Drupal as such provides a "Published" option out-of-the box for nodes, which works similar to "Save Draft" functionality. Nodes that are not published as yet are not available to general audience for viewing and are accessible to authorized users only (those with the "edit any content_type" permission for the corresponding content type).

But the major problem with Drupal's published option is that even if a node is not published (thus meaning that either the author is not finished with it, or the entire content for the node is not available etc), Drupal fires all validations on the node while it is being saved (it does not matter if you have published on or off, Drupal always validates the node content and would not allow saving if validation fails).

This was where a client of mine hit a major hiccup. They were having multiple content types, with a couple of them having insanely large number of data entry fields (needless to say added through CCK to the content types). And it happened that their authors did not have all the required information while they were adding content, i.e. they wanted to add the content to the node fields iteratively as more details become available but the node should be available on the site only when they have completed all the fields properly. Sometimes the authors needed to leave as they were entering data in (pretty long) node forms and hence again wanted the ability to save the form with the currently entered data, come back to it later and complete it.

The ability to make it available on site when desired was easily achievable with the Drupal core's Published flag. The problem was validations on fields that were marked required or other validations on the fields were preventing the form from saving even with "Published" turned off. As already said, the data was either not available to fill-in at the time, or they needed to save and return back to it later.

The essence is I needed to allow them to save the node bypassing all validations (whether they are for core Drupal fields or for fields added by modules like CCK) when the node was not published.

My initial instinct was that this should be a pretty common requirement and some contributed module for Drupal should be available that by-passes all validation providing a true "Save Draft" functionality for nodes. I came across 3 modules on Drupal that went by saying to provide a "Save Draft" functionality for nodes, but none of them bypassed validation. The node was allowed to be saved only when it successfully passed all validations defined on the node.

And I now knew this needed some custom development. Okay so, I said to myself, let's see how can I bend and transform Drupal to make it work for me (I anticipated this might require some real improvisation and hacking into how Drupal works).

My first approach was to replace the default Drupal validation methods for a node form. So I used hook_form_alter and overrode the validation methods on the node form, something like:

 

{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }function mymodule_form_alter(&$form, &$form_state, $form_id) { switch($form_id) { case 'artist_node_form': $form['#validate'] = array('mymodule_custom_validation'); break; } } function mymodule_custom_validation($form, &$form_state) { $status = $form_state['values']['status']; if($status == 1) { //Here I manually called the original validation methods for the node form as the status is Published. //I have not shown in this code, but I preserved the default validation methods array in hook_form_alter above which I now called in a loop. } }{/syntaxhighlighter}

Notice in the above code that I am completely replacing the #validate methods with my own validation method instead of adding to the existing validation methods.

However this approach did not work. I am not sure why but the default validation methods were always called even after I had replaced them with the code above. I tried various forms of this approach, like assigning the validation methods in #after_build method, assigning validation method explicitly for the "Submit" button itself, and other such options based on replacing Drupal's validation methods. But none of this worked (to my surprise).

After not being able to succeed with this approach, I thought of trying another completely different alternative. That of allowing Drupal to carry on its normal process and validations, but after Drupal had done so, manually clear all errors for the node form if the status was unpublished.

So, this time I came up with the following code:

 

{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }function mymodule_form_alter(&$form, &$form_state, $form_id) { switch($form_id) { case 'artist_node_form': $form['#after_build'][] = 'mymodule_after_build'; break; } } function mymodule_after_build($form, &$form_state) { $form['#validate'] = array('mymodule_custom_validation'); return($form); } function mymodule_custom_validation($form, &$form_state) { $status = $form_state['values']['status']; if($status == 0) { //Reset form errors. form_set_error(NULL, '', TRUE); //Clear error messages. drupal_get_messages('error'); } }{/syntaxhighlighter}

And yes, this approach worked perfectly to my great relief. Now here, I first use hook_form_alter for the desired node form to register an #after_build method for the form. In the #after_build method, I further register (rather override) the #validate method for the form (To understand why I register the #validate method in #after_build method and not directly in hook_form_alter method, see my previous blog post here).

In my custom validation method, I first clear all errors that have been registered by the previous validation methods using: form_set_error(NULL, '', TRUE);

The documenation for form_set_error clearly states that passing TRUE as the last argument clears out all errors previously set through a call to form_set_error for the current form submission.

This is actually all we need to do to bypass validation and allow the node form to be saved even when it violates the rules. But to prevent the messages for the validation errors registered earlier (and cleared using form_set_error as explained above), I also placed a call to drupal_get_messages passing 'error' as the type. This removed all 'error' messages that are implicitly created with a call to form_set_error from getting displayed on the next screen. And viola, it works perfectly as desired!!!

I am thinking of rolling this up in a module and publish it on drupal.org. But because of my current too hectic schedule, and the fact that the actual code is hardly 10-15 lines, I have decided to publish the technique to only my blog currently.

Please let me know (via comments below) if you think this deserves to be a module on its own. If enough people agree, I might well release a module for the same.

 

Comments

Great thanks. This is really perfect solution. Can I translate this article to Russian for Russian and Ukrainian Drupal community?

rahul's picture

Hi Oleg, anything that spreads knowledge is welcome. Please feel free to do so but a link to the original article mentioning credits should certainly be there.

Your solution works properly only for Drupal 6.

Function form_set_error() also has three parameters in Drupal 7 API. First two parameters are the same. But third parameter means not the same as at D6 API syntax.

Third parameter is $limit_validation_errors with default value NULL

The right way to bypass validation at Drupal 7 - set these options in submit button array

'#limit_validation_errors' => array(), - disable validation mode
'#submit' => array('some_submit_function'), - set validation function handler for submit button
'#type' => 'submit', - change the button type as 'submit'

rahul's picture

Hi Oleg, as mentioned in the beginning, I did not use this code with D7 yet. Thanks for sharing your solution, certainly helpful...

This solution does not work in D7 since change in core - see http://drupal.org/node/763376 . Unvalidated values are removed and therefore not saved. I found "gray hat" workaround - in form_alter hook ad custom validation function like:

array_unshift($form['#validate'], 'mymodule_validate');

and in this custom validation function disable #limit_validation_errors:

$form_state['triggering_element']['#limit_validation_errors'] = FALSE;

I would be happy for cleaner solution, but found none. There is proposition for one http://drupal.org/node/941620, but neither accepted nor done.

rahul's picture

Thanks Stepan for sharing these details.

Would bypassing the validation affect sanitizing the data at all before saving it to the DB?

rahul's picture

Hmm... I think it might affect security. It would certainly bypass any custom validation rules, but Input filters should still do their work considering that they kick in during rendering phase (don't take my word for it and test it if it jeopardizes security).

Drupal's built in way for checking Sql injection and CSRF checks would not be affected and they would still be performed. Rest it depends upon how checks and sanitizing of data is actually performed? If you use CCK/Fields and a Field uses a validation handler for sanitizing data, it would certaily be by-passed.

But also take note that bypass only happens when the node is not published. When you publish the node and save it, all validations and sanitization would be performed.

I wrote this code for a client's site where only client's authorized staff was able to choose not to publish a node while creating/updating it and hence bypass validation. Regular users did not have this option and hence their submissions always underwent sanitization. You might want to adapt this code and add role-based bypass, so things are bypassed only for trusted roles.

Thanks for sharing

Regards

Interesting and useful site - thank you.

Has anyone come up with a module for this yet?

Thanks

rahul's picture

Well, atleast I am not aware of one, this could be a good opportunity to do it yourself :-)

nice post,thanks for sharing!

Hi,

if i want to call any function using #limit_validation_errors then how actually i want save form values if user put it.

Thanks&regards
fawwad

rahul's picture

Hi Fawwad, I am not exactly sure what you mean. Can you please exlpain more precisely.

I want to create node if person put some wrong values(if i set integer but he put char) in required filed then it will show error message. But if person does not put values in required filed and want to draft the form  then it should save the entered values and skip validation for another fields that are not filled yet with values.

There is select box having option 1. draft 2. Need review. 3.Publish and common "Save button" in bottom.

rahul's picture

Hi Fawwad, the approach from this blog post clears all errors when saving as draft. It won;t distinguish between required fields vs data type mismatch errors. I am sure you can clear errors selectively instead of clearing all errors by writing some custom code.

rahul's picture

Hi Shiva,

Can you elaborate please.