PHP code to generate values for the "allowed values" of a field

mjlF95 - January 19, 2009 - 18:44

I'm trying to generate a list of values that is restricted to sets of 0000-9999 preceded by a two letter country code. Something like us0228 would be valid, but it has to match that format and only for two letter codes that are specified. Here is the test code that generates a list of the values we're looking for for each of the 6 two letter codes specified in array $countries. I've gotten similar code to work in the "allowed values" area but this does not seem to work. Any suggestions? Also, do you think it will be a problem if we populate this with something like 200 countries? That will be like 2,000,000 million values. Recipe for disaster? (Note: This is the first PHP code I've written and I had help with it)

<?php

$count_key
= 1;

function
buildcountry($count_key) {
   
//$count_key = 1;
   
$countries = array(1 => "us", "af", "al", "dz", "as", "ad");
   
$end = 9999;
   
$start = 0000;
   
$allowed_values = array(); 
    while (
$start <= $end):
        if (
strlen($start) == 1):
         
$allowed_values[$start] = $countries[$count_key] . "000" . $start;
         
$start++;
        elseif (
strlen($start) == 2):
         
$allowed_values[$start] = $countries[$count_key] . "00" . $start;
         
$start++;
        elseif (
strlen($start) == 3):
         
$allowed_values[$start] = $countries[$count_key] . "0" . $start;
         
$start++;
        else:
         
$allowed_values[$start] = $countries[$count_key] . $start;
         
$start++;
        endif;
    endwhile; 
   
//$count_key++;
   
   
print_r($allowed_values);
    return
$allowed_values;
    unset(
$allowed_values);
}
buildcountry($count_key++);
buildcountry($count_key++);
buildcountry($count_key++);
buildcountry($count_key++);
buildcountry($count_key++);
buildcountry($count_key++);

?>

Well first off you already

nevets - January 19, 2009 - 18:53

Well first off you already have 60,000 values which if this results in a dropdown will already be a problem.

As for the code it does not work because it does not return anything.

Instead of suggesting a fix for the code what about using two inputs, one for country and one for an integer which can have a minimum and maximum value. If you really need a field with both you could consider using a computed field.

Thanks for the reply. It

mjlF95 - January 19, 2009 - 19:04

Thanks for the reply.

It will not be a dropdown, but I'm concerned about the amount of values anyway.

Really what it needs to do is to be an optional field that let's the user type in a code formatted as I've indicated. I briefly checked out computed fields. If it is as it seems to me, that the computed field would allow a number to be generated, the criteria for that number might not be consistent enough to write code to do it automatically. The user should be the one entering the code, it needs to be optional, and it needs to match the criteria (2 letters and a number between 0000 and 9999).

You said it's not returning anything which is something I was worried about. The print_r($allowed_values); seems to print all the values to the screen if run, but I was worried that using unset might be overwriting the array each time. I tried it without unset but then I started to worry that the array might be retaining values. I'm a bit new to PHP so I'm not entirely confident what to expect.

I would code this

nevets - January 19, 2009 - 19:48

I would code this as

<?php
$countries
= array("us", "af", "al", "dz", "as", "ad");
$allowed_values = array();
foreach (
$countries as $country ) {
    for (
$value = 0; $value <= 9999; $value++ ) {
       
$allowed_values[] = sprintf("%s%04d", $country, $value);
    }
}

print_r($allowed_values);
return
$allowed_values;


?>

Awesome. Thank you very much

mjlF95 - January 19, 2009 - 20:40

Awesome. Thank you very much for re-writing that so concisely. I'll have to study that I bit to see how you did that.

I tested it and it produced the desired results, the same as the previous code. I tried it in drupal however and crashed the site (test site) similarly to before. Could be residual bugs from the old code though. Let me try it again and see if I can figure out what's causing that.

This is so weird. Ok, so I

mjlF95 - January 19, 2009 - 21:24

This is so weird. Ok, so I removed the print_r statement and paired the array down to just "us". I got an out of memory error earlier so I think that the number of values returned is going to be an issue, but I don't want to address that yet. One step at a time. :)

When I enter in an allowed value like "us8888" it says "Illegal value for XXXX". However, if I change the text field to a select list, it shows values us0000-us9999 and if I select one it accepts the value as it should. This seems inconsistent and confusing. Any ideas?

Ok, exactly what field type

nevets - January 19, 2009 - 21:42

Ok, exactly what field type and widget are you using (when not using the select list)?

Type: Text. Widget: Text

mjlF95 - January 19, 2009 - 22:02

Type: Text. Widget: Text Field.

Try

nevets - January 19, 2009 - 22:04

Try changing
$allowed_values[] = sprintf("%s%04d", $country, $value);
to

$str  = sprintf("%s%04d", $country, $value);
$allowed_values[$str] = $str;

Worked beautifully!

mjlF95 - January 19, 2009 - 22:20

Works beautifully. It was really kind of you to take time out of your day to give me a hand with this. I'm really grateful. Anyway, as I suspected I get out of memory errors between the 20,000 and 30,000 mark. Sigh. :P More ideas welcome but I'll try doing some research myself as well. I thought this would be a simple solution to this problem, but maybe it's still a bit too brute force.

An alternative approach,

nevets - January 19, 2009 - 23:41

An alternative approach, visit http://drupal.org/project/validation_api, download and install the module.

First lets add a new validator

Visit Administer › Site building › Validation API › Validators, click the 'add' tab.

Give the validator a name and for 'Type' pick 'PHP'

Under 'Rule' add

if  (  strlen($value) != 6 ) {
  return FALSE;
}

$country_code  =   substr($value, 0, 2);
$value = substr($value,  2);

$countries = array("us", "af", "al", "dz", "as", "ad");

// Make sure we have a valid country  code
if ( !in_array($country_code, $countries) ) {
  return FALSE;
}

// Make sure $value is an integer value
if ( preg_match('@^[0-9]+$@',$value) !== 1 ) {
return FALSE;
}

// Make sure $value is in range
if ( $value <  0 || $value > 9999 ) {
  return FALSE;
}

return TRUE;

The range check is not needed since you accept any 4 digit number but can be useful in other cases

Set 'Default Message' and save.

...

Now visit the add/edit page for the content type where you want to add the validation.

Look below the field you want validated, there should be a link of the form "Add a validator to...", click the link.

It should expand to a set of links, the first should be of the form "Add a validator to field_yourfieldname[0]", with 'yourfieldname' being the name of the field, click.

On the resulting page, under 'Validator' select tha validator you added above, click "Next step".

That looks really useful.

mjlF95 - January 20, 2009 - 01:48

That looks really useful. I'm using 5.x though and it appears to be a 6.x module. This (http://drupal.org/node/203992) may be a 5.x alternative. Let me see if I can patch it.

I'm replying to my original

mjlF95 - January 20, 2009 - 02:21

I'm replying to my original post in hopes that our dialog boxes will not continue to be infinitely compressed. In response to the last post I made about applying the patch from that CCK issue node, the patch applies to version 1.6-1 and I happen to be running the very next version, 1.7. I checked the contents of the patch, and it seems to already be applied to the text.module in CCK version 1.7. Does that mean that validation code of the sort you were talking about (not just an array of values, but a true/false conditional kind of thing) can be entered as is? I'll try to run some tests to determine if this is in fact the case.

Still working on this

mjlF95 - April 2, 2009 - 15:42

So I've been reading Pro Drupal Development and I'm reading up on PHP. I'm still working on this issue. I used the example/tutorial code from Pro Drupal Development and modified the validation function to use a regex that returns an error when there is not a match on the set of values we are looking for. It works properly and it looks like this.

/**
* Validate the form.
*/
function formexample_nameform_validate($form_id, $form_values) {
if preg_grep('/\b(us|af|al|dz|as|ad|ao|ai|aq|ag|ar|am|aw|au|at|az|bs|bh|bd|bb|by|be|bz|bj|bm|bt|bo|ba|bw|bv|br|io|bn|bg|bf|bi|kh|cm|ca|cv|ky|cf|td|cl|cn|cx|cc|co|km|cg|ck|cr|ci|hr|cu|cy|cz|dk|dj|dm|do|tp|ec|eg|sv|gq|er|ee|et|fk|fo|fj|fi|fr|fx|gf|pf|tf|ga|gm|ge|de|gh|gi|gr|gl|gd|gp|gu|gt|gn|gw|gy|ht|hm|hn|hk|hu|is|in|id|ir|iq|ie|il|it|jm|jp|jo|kz|ke|ki|kp|kr|kw|kg|la|lv|lb|ls|lr|ly|li|lt|lu|mo|mk|mg|mw|my|mv|ml|mt|mh|mq|mr|mu|yt|mx|fm|md|mc|mn|ms|ma|mz|mm|na|nr|np|nl|an|nc|nz|ni|ne|ng|nu|nf|mp|no|om|pk|pw|pa|pg|py|pe|ph|pn|pl|pt|pr|qa|re|ro|ru|rw|kn|lc|vc|ws|sm|st|sa|sn|sc|sl|sg|sk|si|sb|so|za|gs|es|lk|sh|pm|sd|sr|sj|sz|se|ch|sy|tw|tj|tz|th|tg|tk|to|tt|tn|tr|tm|tc|tv|ug|ua|ae|uk|um|uy|uz|vu|va|ve|vn|vg|vi|wf|eh|ye|yu|zr|zm|zw)[0-9]{4}\b/', $form_values['user_name']) {
// We notify the form API that this field has faild validation.
form_set_error('user_name',
  t('That value is not allowed on this form.'));
}
}

Next I tried implementing hook_form_alter to apply this to a CCK content type input form on a dev site I have. There is a content type called "game listing" and a field on that called "stock no". I tried getting that field to use this function for validation.

/**
* Implement form_alter to add validation to stock_no
* field on the create a game listing form
*/
function formexample_form_alter($form_id, &$form) {
  if ($form_id == 'game_listing_node_form') {
//  array('formexample_nameform_validate' => array());
//  formexample_nameform_validate($form_id, $form_values);
//  $form['#validate'] = array('formexample_nameform_validate' => array());
  }
}

The bottom two lines were my attempt at reading Pro Drupal Development and modifying the code to say "on the game_listing_node_form use formexample_nameform_validate to validate the field (specified in the function formexample_nameform_validate). The third line from the bottom was me just trying to call the function directly. I got some advice from someone before I wrote this which was to " Just add $form[$element]['#validation'] => array('validation_function_name' => array());". This was very similar to what I tried but a little different so I played around with that too but still could quite get it to work.

What I get is the hook seems to work, it calls the function, but the regex doesn't evaluate properly. Now I've tested this regex already and it works. My suspicion then turned to the value it's working on, in this case $form_values['user_name'] which for this I changed to $form_values['stock_no'] since that's the field on the form I'm trying to alter. So I tried a var_dump on $form_values and it returned NULL. This made sense to me since a !preg_match everything fails validation, and if I change it to preg_match nothing fails validation. Can anyone help me to understand why $form_values is NULL and how I might be able to get this working? I feel like I'm very close but I just don't understand well enough.

EDIT: I finally solved this. I had to use this code for hook form alter:

$form['#validate'] = array('formexample_nameform_validate' => array());

Then my var_dump on $form_values in the validation function actually returned something. From that I was able to find the value I was looking for. I was referencing ['stock_no'] when I needed to reference ['field_stock_no'][0]['value']. Once I did that everything worked fine.

 
 

Drupal is a registered trademark of Dries Buytaert.