Trim required fields on validate

quicksketch - February 10, 2007 - 05:36
Project:Drupal
Version:5.x-dev
Component:forms system
Category:bug report
Priority:normal
Assigned:Unassigned
Status:closed
Description

The #required flag has an interesting (errant) behavior when dealing with textareas. Take comments for example: Go to any Drupal site and post a comment (this issue will work also) with nothing but a carriage return in the description or comment body. Even though the field is required, a single return is considered enough content to slip past the 'Required' field. Stranger still, the content is trimmed before it is inserted into the database, resulting in a completely empty post even though the field is required.

I've been seeing this issue mostly with comments, we get 5-10 anonymous empty comments a day by bots. Would trimming the field value be a better solution than using empty()?

Index: /Users/nate/Sites/lullabot/v1/public_html/includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.174.2.3
diff -u -r1.174.2.3 form.inc
--- form.inc 29 Jan 2007 21:51:53 -0000 1.174.2.3
+++ form.inc 10 Feb 2007 05:24:34 -0000
@@ -526,10 +526,7 @@
   /* Validate the current input */
   if (!isset($elements['#validated']) || !$elements['#validated']) {
     if (isset($elements['#needs_validation'])) {
-      // An empty textfield returns '' so we use empty(). An empty checkbox
-      // and a textfield could return '0' and empty('0') returns TRUE so we
-      // need a special check for the '0' string.
-      if ($elements['#required'] && empty($elements['#value']) && $elements['#value'] !== '0') {
+      if ($elements['#required'] && strlen(trim($elements['#value'])) == 0) {
         form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
       }

Same patch also attached.

AttachmentSize
required_trim.patch1004 bytes

#1

webchick - February 10, 2007 - 15:22
Version:5.x-dev» 6.x-dev
Status:patch (code needs review)» patch (code needs work)

This seems very reasonable to me, except this problem also exists in HEAD and should be fixed there first. Also, the patch complains about unexpectedly ending. Here's a re-roll.

Now the only kind of weird thing is if I for example put in [space][space] as the title of a node, It will kick back and tell me "Title is required", but the [space][space] is still there, so the cursor appears more in the middle of the box -- I have to delete the two spaces in order to add my title. This does not happen, however, when I hit [return] in a comment body; there it puts the cursor right at the beginning of the textarea, as expected (so somewhere along the line it trimmed the input). We should standardize this behaviour.

Also, where did you see these things getting trimmed before they went in the db? We should remove that code if it's duplicate.

AttachmentSize
required_trim_0.patch1013 bytes

#2

quicksketch - February 13, 2007 - 06:02

Thanks webchick. Not sure where things were getting trimmed before they went into the DB. I checked for string and regex replacements but couldn't find why this was taking place. Fortunately this patch (after you fixed it) mostly takes care of that problem. Now entering only returns or spaces into a required field will form_set_error on that field and leave the spaces or returns in place.

I like this approach more than actually trimming the value and making it empty again. Leaving the spaces in there but still throwing an error is inherently telling the user "spaces aren't enough for a title, dummy". Whereas if we trim the title back to an empty string then throw the error, the user will think "I put spaces in there didn't I?", thinking that might be enough. So in general we're avoiding modifying the user's input and instead just telling them it's not enough.

Unfortunately, in textareas if you enter a series of returns, every time you preview the post it looses another return. I'd like to figure that out before committing this patch.

#3

Pancho - February 1, 2008 - 02:48
Status:patch (code needs work)» patch (code needs review)

Here is a rerolled patch. Now we need to figure out what seems to happen with returns in textareas (see #2)... I need to figure out how to test this, as CCK is broken right now.

AttachmentSize
required_trim-3.patch967 bytes

#4

Pancho - February 1, 2008 - 04:18
Category:task» bug report

Required textareas are a rare thing in core. Forum posts with their required body are a good example to test.
If I require 10 (or something) words for any other content type, I'm testing a different validation than the required field.

Results of the testing: The "losing a return" problem is still present, but it is obviously not connected to this patch. It also arises, when a completely different validation routine throws an error or the form is finally saved to db. I also experienced this bug without this patch being applied. Therefore I filed a new ticket #216588.

As the bug we hit is not connected to this patch, I think the patch in #3 is rtbc.
I do consider this a usability bug, for Title fields it is even critical.

#5

pwolanin - February 4, 2008 - 04:14
Status:patch (code needs review)» patch (reviewed & tested by the community)

code looks good, though a code comment might be nice.

Works as advertised.

#6

Gábor Hojtsy - February 4, 2008 - 10:24
Status:patch (reviewed & tested by the community)» fixed

Hm, added some short docs and committed with that.

      // We only check for trimmed string length being zero. 0 might be
      // a possible value with some field types, such as radio buttons, so
      // empty() is not usable here.

#7

webernet - February 4, 2008 - 16:01
Status:fixed» active

During Install:

notice: Array to string conversion in /includes/form.inc on line 670.

#8

Gábor Hojtsy - February 4, 2008 - 16:25
Status:active» patch (code needs work)

Doh, rolled this back. For reference, this was the patch I committed originally: http://cvs.drupal.org/viewvc.py/drupal/drupal/includes/form.inc?r1=1.264...

#9

Rob Loach - February 4, 2008 - 17:23
Status:patch (code needs work)» patch (code needs review)

Will it get the desired effect if we run a is_string on it? Might break something else, but worth attempting.

AttachmentSize
required_trim-4.patch836 bytes

#11

pwolanin - February 4, 2008 - 17:28
Status:patch (code needs review)» patch (code needs work)

gak, sorry about that - was focused on testing text fields. empty() does make sense for array elements, but causes its own problems.

how about something more like:

if ($elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0))) {

for any string (even an empty string) count() returns 1,

#12

Rob Loach - February 4, 2008 - 17:29
Status:patch (code needs work)» patch (code needs review)

That made no sense. This will check if the value is actually a string, and then check if it's an empty string. This might give the desired result. Testing is definitely needed.

AttachmentSize
required_trim-5.patch1.12 KB

#13

Rob Loach - February 4, 2008 - 17:34
Status:patch (code needs review)» patch (code needs work)

Nope...

#14

pwolanin - February 4, 2008 - 17:39

I don't know what webchick was posting, but I crossposted with both above.

@Rob Loach - I don't see that your code properly handles arrays - for example checkboxes that are required.

#15

Rob Loach - February 4, 2008 - 17:39
Status:patch (code needs work)» patch (code needs review)

This is a bit better....

AttachmentSize
required_trim-6.patch1.29 KB

#16

Rob Loach - February 4, 2008 - 17:41
Status:patch (code needs review)» patch (code needs work)

It seems like we should provide a switch statement to handle all required use cases (string, arrays, integers?).

#17

Pancho - February 4, 2008 - 18:23

Rob Loach: Guess that's a good idea, as this is more transparent and thus also prevents future errors.

#18

Pancho - February 4, 2008 - 18:50

Something like this would be cool:

switch ($elements['#datatype']) {
  case 'string':
    $empty = strlen(trim($elements['#value'])) == 0;
    break;
  case 'array':
    $empty = empty($elements['#value']);
    break;
  default:             // case 'integer':
    $empty = isset($elements['#value']);
}
if ($empty) {
  form_error($elements, $t('!name field is required.', array('!name' => $elements['#title'])));
}

but we don't have something like $elements['#datatype']. Good ideas?

#19

pwolanin - February 4, 2008 - 20:38

see my comments above in #11- this last patch code is wrong since it fails to check arrays that may be empty

#20

pwolanin - February 4, 2008 - 23:51
Status:patch (code needs work)» patch (code needs review)

@Gabor - the rollback does not seem to have worked.

attached patch per the code in #11, seems to work for both checkboxes and textfields

AttachmentSize
fix-trim-117748-20.patch887 bytes

#21

Pancho - February 5, 2008 - 00:29

#20 should work, but we better check twice...
I just split the if-expression in two, as it was hard to grasp. Also I updated documentation.

AttachmentSize
required-trim_21.patch1.43 KB

#22

pwolanin - February 5, 2008 - 01:16

@Gabor - or - perhaps you rolled-back on 6.x and not HEAD?

#23

pwolanin - February 5, 2008 - 02:04

@Panch - your docs are not correct: "Count() checks if the item (string, array, integer) contains anything."

Patches attached for HEAD and DRUPAL-6

AttachmentSize
fix-trim-117748-23-HEAD.patch1.22 KB
fix-trim-117748-23-6x.patch1.46 KB

#24

Pancho - February 5, 2008 - 02:52

Another suggestion for the docs:

// Validate required form items:
// Count() discards empty arrays and NULL values but not empty strings.
// Strings are checked not to be empty after being trimmed.

Also, I still prefer separate if-clauses for if ($elements['#required']) and the actual validation (see patch #21).

#25

Gábor Hojtsy - February 5, 2008 - 08:56

Yes, I rolled back on 6.x, I am not entitled to commit anything to HEAD. Once we figure out how to solve this, it needs to be ported to HEAD too (or if we don't figure out anything, HEAD needs to roll back the patch as well).

#26

pwolanin - February 5, 2008 - 14:00

@Gabor - I think the code in #23 works well, so it's just a question of the code comments.

#27

Rob Loach - February 5, 2008 - 16:08
Priority:normal» critical

This has to be fixed before the release of Drupal 6.0 as all these warnings when submitting forms would drive most users away. So, I'm setting this to critical.

I've tested the code from #23 in both a checkbox and textfield situation and both seemed to work as desired. The textfield where I passed an empty string was denied, and the checkboxes that I didn't check a required value were denied. I tested this in admin/settings/blogapi and admin/settings/site-information.

In my opinion, the code comments from #23 are fine, but this might seem more helpful for someone reading through the code (see patch)....

AttachmentSize
required_trim-27.patch1.47 KB

#28

Pancho - February 5, 2008 - 16:46

Which warnings? The erroneous patch has been rolled back, so we are in the previous state, at least in the D6 branch.
Demoting this to normal. If I misunderstood you, feel free to make this critical again.
Even if not critical, we should get this committed soon, both in the D6 branch and in HEAD. But no rushing in - this time it should be 100%.

#29

Pancho - February 5, 2008 - 16:47
Priority:critical» normal

#30

webernet - February 7, 2008 - 01:38

Did a quick test of #23, and it doesn't appear to break anything.

#31

Pancho - February 8, 2008 - 03:24
Status:patch (code needs review)» patch (reviewed & tested by the community)

#23 and #27 is essentially the same code. There are two differences:

  1. #27 is more verbose in separating out the "if ($elements['#required'])" check, which I personally prefer.
  2. A different wording of the documentation.

This is nothing we can decide. Therefore it would be great if Gábor or Dries would take the decision and commit either of the two patches.
I mark this RTBC, as this is all we can do.

#32

chx - February 8, 2008 - 15:46

I do *not* like the verbosity in #27. Thats code bloat in my eyes.

#33

Rob Loach - February 8, 2008 - 16:03
Status:patch (reviewed & tested by the community)» patch (code needs review)
AttachmentSize
trim_required-117748-33.patch1.31 KB

#34

Gábor Hojtsy - February 8, 2008 - 16:05

Why do we only need to check for arrays and strings? Can someone shed a light on this?

#35

pwolanin - February 8, 2008 - 18:00

@Gabor - data coming out of FAPI is always a string or an array. count(NULL) == 0, so that works. If you run trim() on a number or boolean, it's converted to a string, so that works too.

if someone really wants to return an object from a form(?!), they could implement the "countable" SPL interface. "This extension is available and compiled by default in PHP 5.":

http://us.php.net/manual/en/function.count.php

#36

Gábor Hojtsy - February 8, 2008 - 18:05
Version:6.x-dev» 7.x-dev
Status:patch (code needs review)» patch (reviewed & tested by the community)

pwolanin: thanks for the explanation. Committed #33 to Drupal 6. Still needs to be committed to 7.x.

#37

pwolanin - February 8, 2008 - 18:15
Version:7.x-dev» 6.x-dev
Status:patch (reviewed & tested by the community)» patch (code needs review)

So the comment could be clarified, maybe like:


+ // If the field is required, empty() cannot be used to check since
+ // the string '0' may be valid. count() gives the number of
+ // elements in an array or countable object, 0 for NULL, or 1.
+ // trim() removes white space, and converts to string.

#38

pwolanin - February 8, 2008 - 18:16
Version:6.x-dev» 7.x-dev
Status:patch (code needs review)» patch (reviewed & tested by the community)

oops- cross posted

#39

Senpai - February 11, 2008 - 00:34
Status:patch (reviewed & tested by the community)» patch (code needs work)

The error in #7 is still present during the installation of head. FYI

notice: Array to string conversion in /includes/form.inc on line 670.

#40

pwolanin - February 11, 2008 - 01:16
Status:patch (code needs work)» patch (reviewed & tested by the community)

@Senpai - the bad patch still needs to be rolled back in HEAD and the good patch applied. This is only fixed in 6.x

#41

pwolanin - February 11, 2008 - 02:22

here's a version of the patch from #33 that applies to HEAD.

AttachmentSize
trim_required-117748-33-7x.patch1.3 KB

#42

Rob Loach - February 11, 2008 - 06:43

Looks good.

#43

chx - February 15, 2008 - 04:07

Until this is committed , HEAD will display a notice on install.

#44

pwolanin - February 20, 2008 - 04:12

bump - RTBC and causing errors in HEAD

#45

pwolanin - February 24, 2008 - 22:56

bump again...

#46

Senpai - March 2, 2008 - 17:36

Bump a third time...

#47

vladimir.dolgopolov - March 3, 2008 - 09:29

Here is the test for the issue.
Before #41 there were 31 passes, 2 fails and 2 exceptions.
After #41 there were 33 passes, 0 fails and 0 exceptions.

<?php
class TestCase117748 extends DrupalTestCase {
  function
get_info() {
    return array(
     
'name' => t('[117748] Trim required fields on validate'),
     
'desc' => t('The #required flag has an interesting (errant) behavior when dealing with textareas. Take comments for example: Go to any Drupal site and post a comment (this issue will work also) with nothing but a carriage return in the description or comment body. Even though the field is required, a single return is considered enough content to slip past the \'Required\' field. Stranger still, the content is trimmed before it is inserted into the database, resulting in a completely empty post even though the field is required. /quicksketch/'),
     
'group' => t('Drupal 7 Tests'),
    );
  }
 
 
// Check several empty values on standard forms' elements.
  // If the form field found in form_get_errors() then the test pass.
 
function testIssue() {

   
// Sets of empty strings and arrays
   
$empty_strings = array("", "\n", " ", "\t", " \n\t ", "\n\n\n\n\n");
   
$empty_arrays = array(array());

   
$elements['textfield']['element'] = array('#title' => $this->randomName(), '#type' => 'textfield', '#required' => TRUE);
   
$elements['textfield']['empty_values'] = $empty_strings;

   
$elements['password']['element'] = array('#title' => $this->randomName(), '#type' => 'password', '#required' => TRUE);
   
$elements['password']['empty_values'] = $empty_strings;

   
$elements['password_confirm']['element'] = array('#title' => $this->randomName(), '#type' => 'password_confirm', '#required' => TRUE);
   
$elements['password_confirm']['empty_values'] = $empty_strings;

   
$elements['textarea']['element'] = array('#title' => $this->randomName(), '#type' => 'textarea', '#required' => TRUE);
   
$elements['textarea']['empty_values'] = $empty_strings;

   
$elements['radios']['element'] = array('#title' => $this->randomName(), '#type' => 'radios', '#required' => TRUE, '#options' => array($this->randomName(), $this->randomName(), $this->randomName()));
   
$elements['radios']['empty_values'] = $empty_arrays;

   
$elements['checkboxes']['element'] = array('#title' => $this->randomName(), '#type' => 'checkboxes', '#required' => TRUE,'#options' => array($this->randomName(), $this->randomName(), $this->randomName()));
   
$elements['checkboxes']['empty_values'] = $empty_arrays;

   
$elements['select']['element'] = array('#title' => $this->randomName(), '#type' => 'select', '#required' => TRUE, '#options' => array($this->randomName(), $this->randomName(), $this->randomName()));
   
$elements['select']['empty_values'] = $empty_strings;

   
$elements['file']['element'] = array('#title' => $this->randomName(), '#type' => 'file', '#required' => TRUE);
   
$elements['file']['empty_values'] = $empty_strings;

   
// Go through all the elements and all the empty values for them
   
foreach ($elements as $type => $data) {
     
$i = 1;
      foreach (
$data['empty_values'] as $empty) {
       
$form_id = $this->randomName();
       
$form = $form_state = array();
       
$form['op'] = array('#type' => 'submit', '#value' => t('Submit'));
       
$element = $data['element']['#title'];
       
$form[$element] = $data['element'];
       
$form_state['values'][$element] = $empty;
       
$form['#post'] = $form_state['values'];
       
$form['#post']['form_id'] = $form_id;
       
drupal_prepare_form($form_id, $form, $form_state);
       
drupal_process_form($form_id, $form, $form_state);
       
$errors = form_get_errors();
       
$this->assertTrue(isset($errors[$element]), "Check empty(".$i++.") '$type' field '$element'");
      }
    }
   
// Avoid output drupal warnings and messages into assert messages
   
drupal_get_messages();
  }

}
?>

#48

catch - March 12, 2008 - 00:26
Title:Trim required fields on validate» admin/user/user/create is broken
Priority:normal» critical

http://drupal.org/node/232763 was duplicate.

Also this is blocking progress on http://drupal.org/node/228594 since admin/user/user/create is broken

#49

Dries - March 13, 2008 - 20:58
Status:patch (reviewed & tested by the community)» patch (code needs work)

I'm not a big fan of this patch. I think it's clumsy. Furthermore, I think we should be using drupal_strlen() instead of strlen(). Please verify. Thanks.

#50

pwolanin - March 13, 2008 - 21:42

@Dries - strlen() should be fine (and much faster) since we just want a boolean answer of "characters or no characters".

Do you have suggestions? I think it's less clumsy than what we had before.

#51

chx - March 13, 2008 - 23:15

Clumsy...? Call PHP clumsy, then.

#52

pwolanin - March 14, 2008 - 21:31
Status:patch (code needs work)» patch (reviewed & tested by the community)

@Dries - this patch is already in D6, and fixes a critical bug. If you have suggestions for better code for D6 and or D7, can they be addressed after HEAD works again?

#53

Dries - March 15, 2008 - 11:02
Priority:critical» normal
Status:patch (reviewed & tested by the community)» patch (code needs work)

OK, I've committed this to CVS HEAD to make HEAD work again. I'm marking this 'code needs work' as the test needs to be extracted into a simpletest test.

#54

pwolanin - March 15, 2008 - 18:46

@Dries - thanks for getting the fix in, but I'm confused about what tests you are referring to?

#55

catch - March 15, 2008 - 21:27

pwolanin - did you see the test in #47

#56

pwolanin - March 15, 2008 - 23:56
Status:patch (code needs work)» fixed

oh, right, THAT test.

Making minor changes to the test and posting as a patch: http://drupal.org/node/231302

#57

pwolanin - March 21, 2008 - 02:07
Title:admin/user/user/create is broken» Trim required fields on validate
Version:7.x-dev» 5.x-dev
Status:fixed» patch (to be ported)

how about 5.x?

#58

igorik - March 22, 2008 - 08:40
Priority:normal» critical

Yes, there is a little missunderstood which patch from all of them here is ok to apply to Drupal 5,
is it #23 - http://drupal.org/node/117748#comment-717485 ?

I have critical problem with empty nodes atomaticaly created when some user normal goes through site, he add no content...
so mabe this can be solution for it (http://drupal.org/node/236986)

thanks
Igorik
http://www.somvprahe.sk

#59

pwolanin - March 22, 2008 - 13:08

none of these patches work for 5.x, hence the status "to be ported"

#60

pwolanin - March 22, 2008 - 13:11
Status:patch (to be ported)» patch (code needs review)

here's an UNTESTED patch for 5.x

AttachmentSize
fix-trim-117748-60-5x.patch1.35 KB

#61

drumm - April 3, 2008 - 06:29
Priority:critical» normal

I do not think this is critical, it is only an edge case.

@igorik, maybe you should check with your problem users or see if a contributed module or somesuch is causing the problem.

#62

pwolanin - April 3, 2008 - 13:34

@drumm - right, sorry, it was critical for HEAD since some forms were failing there due to the bad 1st patch that was committed.

#63

janusman - July 15, 2008 - 16:26

subscribing

#64

irstudio - July 21, 2008 - 16:45
Status:patch (code needs review)» patch (reviewed & tested by the community)

Applied #60 to my Drupal 5.8 installation.

Doesn't appear to break anything and looks it is a very welcome bugfix that allows me to remove all the duplicate validation code in my module that is currently checking for empty values in required fields.

#65

janusman - July 25, 2008 - 19:40

Ditto, it works here too =)

#66

drumm - August 4, 2008 - 04:00
Status:patch (reviewed & tested by the community)» fixed

Committed to 5.x.

#67

Anonymous (not verified) - August 18, 2008 - 04:02
Status:fixed» closed

Automatically closed -- issue fixed for two weeks with no activity.

 
 

Drupal is a registered trademark of Dries Buytaert.