Country-State Selector AJAX 1.5

An osCommerce Contribution

'insaini' adapted from previous version by Steve Lionel ("stevel")

Introduction

The Country-State Selector contribution provides the following enhancements to the Create Account and Address Book features of osCommerce:

States With Zones

If you have states with zones, the customer will be presented with a list of the zones. The check in the stock osCommerce to see if a customer needs to go back and specify a zone is removed with this contribution. To ensure that the customer does not select the "plain" state, remove that zone from the database through the Admin panel, if desired.

Optional Admin Feature

NOTE: Admin area does not have AJAX functionality

Due to popular request, I have added selector functionality to the Customers..Edit page in admin. This is not required for the basic store functionality.

Purchase Without Account

If you also use the Purchase Without Account contribution, see its "extras" folder for files to be used along with Country-State Selector. My versions of those files have been removed from Country-State Selector as they're now duplicates.

If you use this combination, you may want to change the define HEADING_TITLE in catalog/includes/languages/<language_name>/create_account.php to 'My Information' or something similar.

Debugging Tips

The following tips may help if the page does not refresh properly when changing the country. If you need further help, ask in the support thread:

Indicator

This line of code

<div id="indicator"><?php echo tep_image(DIR_WS_IMAGES . 'indicator.gif'); ?></div>

is found in these files

Place them wherever you feel is best for your site

ALSO:
Add the indicator css tag to your stylesheet or to each individual page if you would like to position it differently for each page.

  #indicator {
    visibility:hidden;
}

Single-Country Modification

Several users have asked how to use this contribution to provide the state pulldown but for a single country only. To do this, you'll need to modify catalog/create_account.php, catalog/includes/address_book_details.php and catalog/includes/checkout_new_address.php. The change in each of these is similar - the one for create_account.php is shown:

Replace this code:

<tr>
<td class="main"><?php echo ENTRY_COUNTRY; ?></td>
<?php // +Country-State Selector ?>
<td class="main"><?php echo tep_get_country_list('country',$country,'onChange="return refresh_form(create_account);"') . ' ' . (tep_not_null(ENTRY_COUNTRY_TEXT) ? '<span class="inputRequirement">' . ENTRY_COUNTRY_TEXT . '</span>': ''); ?></td>
<?php // -Country-State Selector ?>
</tr>

with this:

<?php echo tep_draw_hidden_field('country',DEFAULT_COUNTRY); ?>

Multiple Country Drop-Down (eg, Shipping Address and Billing Address selection on one page)

In order for this to work you must make a few minor modifications. First you must modify the <DIV id="states"> which surrounds the States drop-down code. For each dropdown in the case of billing and shipping dropdowns .. change it so that it reads <DIV id="billing_states"> and <DIV id="shipping_states"> .. You must then also modify the seperate Country down-downs so that the function call to getStates includes the new DIV id values. Example below

<tr>
<td class="main" width="25%"><?php echo ENTRY_STATE; ?></td>
<td class="main"><div id="billing_states">
<?php
// +Country-State Selector
echo ajax_get_zones_html($country,'',false);
// -Country-State Selector
?>
</div></td>
</tr>
<?php
}
?>
<tr>
<td class="main" ><?php echo ENTRY_COUNTRY; ?></td>
<?php // +Country-State Selector ?>
<td class="main" ><?php echo tep_get_country_list('country',$country,'onChange="getStates(this.value, \'billing_states\');"') . '&nbsp;' . (tep_not_null(ENTRY_COUNTRY_TEXT) ? '<span class="inputRequirement">' . ENTRY_COUNTRY_TEXT . '</span>': ''); ?></td>
<?php // -Country-State Selector ?>
</tr>

 

File List

This contribution is based on osCommerce 2.2-RC2a.

The following files are no added (Copy them to their respective folders):

The following files have changes:

Included in this ZIP file are the changed files based on 2.2-RC2a. The changes are also described in the following sections.

Individual File Changes

For each file's edits, line numbers, where shown, are for positions in the original 2.2-RC2a files. If you are editing files as you go, the line numbers won't match up exactly with your edited file.

catalog/address_book_process.php

Line 13

Find:

require('includes/application_top.php');

Add Below:

 // +Country-State Selector
require(DIR_WS_FUNCTIONS . 'ajax.php');
  if (isset($HTTP_POST_VARS['action']) && $HTTP_POST_VARS['action'] == 'getStates' && isset($HTTP_POST_VARS['country'])) {
ajax_get_zones_html(tep_db_prepare_input($HTTP_POST_VARS['country']), true);
} else {
// -Country-State Selector

Line 99

Replace:

    if (ACCOUNT_STATE == 'true') {
      $zone_id = 0;
      $check_query = tep_db_query("select count(*) as total from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "'");
      $check = tep_db_fetch_array($check_query);
      $entry_state_has_zones = ($check['total'] > 0);
      if ($entry_state_has_zones == true) {
        $zone_query = tep_db_query("select distinct zone_id from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "' and (zone_name = '" . tep_db_input($state) . "' or zone_code = '" . tep_db_input($state) . "')");
        if (tep_db_num_rows($zone_query) == 1) {
          $zone = tep_db_fetch_array($zone_query);
          $zone_id = $zone['zone_id'];
        } else {
          $error = true;

          $messageStack->add('addressbook', ENTRY_STATE_ERROR_SELECT);
        }
      } else {
        if (strlen($state) < ENTRY_STATE_MIN_LENGTH) {
          $error = true;

          $messageStack->add('addressbook', ENTRY_STATE_ERROR);
        }
      }
    }

with:

    if (ACCOUNT_STATE == 'true') {
      // +Country-State Selector
      if ($zone_id == 0) {
      // -Country-State Selector

        if (strlen($state) < ENTRY_STATE_MIN_LENGTH) {
          $error = true;

          $messageStack->add('addressbook', ENTRY_STATE_ERROR);
        }
      }
    }

Line 224

Replace:

    $entry = array();

with:

    $entry = array();
    // +Country-State Selector
    if (!isset($country)) $country = DEFAULT_COUNTRY;
    $entry['entry_country_id'] = $country;
    // -Country-State Selector

Line ~ 271

Find:

<?php
if (!isset($HTTP_GET_VARS['delete'])) {
include('includes/form_check.js.php'); } <?

Replace with:

<?php
if (!isset($HTTP_GET_VARS['delete'])) {
include('includes/form_check.js.php');
// +Country-State Selector
require('includes/ajax.js.php');
// -Country-State Selector
}
?>

At the very end of the page

Find:

<?php require(DIR_WS_INCLUDES . 'application_bottom.php'); ?>

Replace with:

<?php require(DIR_WS_INCLUDES . 'application_bottom.php'); ?>
<?php
// +Country-State Selector
}
// -Country-State Selector
?>
 

catalog/checkout_payment_address.php

Line 13

Find:

require('includes/application_top.php');

Add Below:

 // +Country-State Selector
require(DIR_WS_FUNCTIONS . 'ajax.php');
  if (isset($HTTP_POST_VARS['action']) && $HTTP_POST_VARS['action'] == 'getStates' && isset($HTTP_POST_VARS['country'])) {
ajax_get_zones_html(tep_db_prepare_input($HTTP_POST_VARS['country']), true);
} else {
// -Country-State Selector

Line 93

Replace

        $zone_id = 0;
        $check_query = tep_db_query("select count(*) as total from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "'");
        $check = tep_db_fetch_array($check_query);
        $entry_state_has_zones = ($check['total'] > 0);
        if ($entry_state_has_zones == true) {
          $zone_query = tep_db_query("select distinct zone_id from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "' and (zone_name like '" . tep_db_input($state) . "%' or zone_code like '%" . tep_db_input($state) . "%')");
          if (tep_db_num_rows($zone_query) == 1) {
            $zone = tep_db_fetch_array($zone_query);
            $zone_id = $zone['zone_id'];
          } else {
            $error = true;

            $messageStack->add('checkout_address', ENTRY_STATE_ERROR_SELECT);
          }
        } else {
            $error = true;

            $messageStack->add('checkout_address', ENTRY_STATE_ERROR_SELECT);
          }
        } else {
          if (strlen($state) < ENTRY_STATE_MIN_LENGTH) {
            $error = true;

            $messageStack->add('checkout_address', ENTRY_STATE_ERROR);
          }
        }
      }

with:

    if (ACCOUNT_STATE == 'true') {
      // +Country-State Selector
      if ($zone_id == 0) {
      // -Country-State Selector

        if (strlen($state) < ENTRY_STATE_MIN_LENGTH) {
          $error = true;

          $messageStack->add('checkout_address', ENTRY_STATE_ERROR);
        }
      }
    }

Line 187

Replace:

// if no billing destination address was selected, use their own address as default
 if (!tep_session_is_registered('billto')) {
   $billto = $customer_default_address_id;
 }

with:

// if no billing destination address was selected, use their own address as default
  if (!tep_session_is_registered('billto')) {
    $billto = $customer_default_address_id;
  }
  // +Country-State Selector 

  if (!isset($country)){$country = DEFAULT_COUNTRY;}
  // -Country-State Selector

Line ~ 271

Find:

<?php require(DIR_WS_INCLUDES . 'form_check.js.php'); ?>

Replace with:

<?php 
require(DIR_WS_INCLUDES . 'form_check.js.php');
require(DIR_WS_INCLUDES . 'ajax.js.php');
?>

At the very end of the page

Find:

<?php require(DIR_WS_INCLUDES . 'application_bottom.php'); ?>

Replace with:

<?php require(DIR_WS_INCLUDES . 'application_bottom.php'); ?>
<?php
// +Country-State Selector
}
// -Country-State Selector
?>
 

catalog/checkout_shipping_address.php

Line 13

Find:

require('includes/application_top.php');

Add Below:

 // +Country-State Selector
require(DIR_WS_FUNCTIONS . 'ajax.php');
  if (isset($HTTP_POST_VARS['action']) && $HTTP_POST_VARS['action'] == 'getStates' && isset($HTTP_POST_VARS['country'])) {
ajax_get_zones_html(tep_db_prepare_input($HTTP_POST_VARS['country']), true);
} else {
// -Country-State Selector

Line 93

Replace

        $zone_id = 0;
        $check_query = tep_db_query("select count(*) as total from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "'");
        $check = tep_db_fetch_array($check_query);
        $entry_state_has_zones = ($check['total'] > 0);
        if ($entry_state_has_zones == true) {
          $zone_query = tep_db_query("select distinct zone_id from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "' and (zone_name like '" . tep_db_input($state) . "%' or zone_code like '%" . tep_db_input($state) . "%')");
          if (tep_db_num_rows($zone_query) == 1) {
            $zone = tep_db_fetch_array($zone_query);
            $zone_id = $zone['zone_id'];
          } else {
            $error = true;

            $messageStack->add('checkout_address', ENTRY_STATE_ERROR_SELECT);
          }
        } else {
            $error = true;

            $messageStack->add('checkout_address', ENTRY_STATE_ERROR_SELECT);
          }
        } else {
          if (strlen($state) < ENTRY_STATE_MIN_LENGTH) {
            $error = true;

            $messageStack->add('checkout_address', ENTRY_STATE_ERROR);
          }
        }
      }

with:

    if (ACCOUNT_STATE == 'true') {
      // +Country-State Selector
      if ($zone_id == 0) {
      // -Country-State Selector

        if (strlen($state) < ENTRY_STATE_MIN_LENGTH) {
          $error = true;

          $messageStack->add('checkout_address', ENTRY_STATE_ERROR);
        }
      }
    }

Line 187

Replace:

// if no shipping destination address was selected, use their own address as default
 if (!tep_session_is_registered('billto')) {
   $billto = $customer_default_address_id;
 }

with:

// if no shipping destination address was selected, use their own address  as default
   if (!tep_session_is_registered('sendto')) {
     $sendto = $customer_default_address_id;
   }
  // +Country-State Selector 

  if (!isset($country)){$country = DEFAULT_COUNTRY;}
  // -Country-State Selector

Line ~ 271

Find:

<?php require(DIR_WS_INCLUDES . 'form_check.js.php'); ?>

Replace with:

<?php 
require(DIR_WS_INCLUDES . 'form_check.js.php');
require(DIR_WS_INCLUDES . 'ajax.js.php');
?>

At the very end of the page

Find:

<?php require(DIR_WS_INCLUDES . 'application_bottom.php'); ?>

Replace with:

<?php require(DIR_WS_INCLUDES . 'application_bottom.php'); ?>
<?php
// +Country-State Selector
}
// -Country-State Selector
?>

catalog/create_account.php

Line 13

Find:

require('includes/application_top.php');

Add Below:

 // +Country-State Selector
require(DIR_WS_FUNCTIONS . 'ajax.php');
  if (isset($HTTP_POST_VARS['action']) && $HTTP_POST_VARS['action'] == 'getStates' && isset($HTTP_POST_VARS['country'])) {
ajax_get_zones_html(tep_db_prepare_input($HTTP_POST_VARS['country']), true);
} else {
// -Country-State Selector

Line 130

Replace:

      $zone_id = 0;
      $check_query = tep_db_query("select count(*) as total from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "'");
      $check = tep_db_fetch_array($check_query);
      $entry_state_has_zones = ($check['total'] > 0);
      if ($entry_state_has_zones == true) {
      $zone_query = tep_db_query("select distinct zone_id from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "' and (zone_name like '" . tep_db_input($state) . "%' or zone_code like '%" . tep_db_input($state) . "%')");
      if (tep_db_num_rows($zone_query) == 1) {
          $zone = tep_db_fetch_array($zone_query);
          $zone_id = $zone['zone_id'];
        } else {
          $error = true;
          $messageStack->add('create_account', ENTRY_STATE_ERROR_SELECT);
        }
      } else {
        } else {
        if (strlen($state) < ENTRY_STATE_MIN_LENGTH) {
          $error = true;

          $messageStack->add('create_account', ENTRY_STATE_ERROR);
        }
      }
    }    
      

with:

    if (ACCOUNT_STATE == 'true') {
      // +Country-State Selector
      if ($zone_id == 0) {
      // -Country-State Selector

        if (strlen($state) < ENTRY_STATE_MIN_LENGTH) {
          $error = true;

          $messageStack->add('create_account', ENTRY_STATE_ERROR);
        }
      }
    }

Line 252

Replace:

  $breadcrumb->add(NAVBAR_TITLE, tep_href_link(FILENAME_CREATE_ACCOUNT, '', 'SSL'));

with:

 // +Country-State Selector 

 if (!isset($country)) $country = DEFAULT_COUNTRY;
 // -Country-State Selector

 $breadcrumb->add(NAVBAR_TITLE, tep_href_link(FILENAME_CREATE_ACCOUNT,   '', 'SSL'));
 

Line ~ 260

Find:

<?php require(DIR_WS_INCLUDES . 'form_check.js.php'); ?>

Replace with:

<?php 
require(DIR_WS_INCLUDES . 'form_check.js.php');
require(DIR_WS_INCLUDES . 'ajax.js.php');
?>

Line 355

Replace:

    <!-- body_text //-->
<td width="100%" valign="top"><?php echo tep_draw_form('create_account', tep_href_link(FILENAME_CREATE_ACCOUNT, '', 'SSL'), 'post', 'onSubmit="return check_form(create_account);"') . tep_draw_hidden_field('action', 'process'); ?>
<table border="0" width="100%" cellspacing="0" cellpadding="0" >

with:

    <!-- body_text //-->
<td width="100%" valign="top"><?php echo tep_draw_form('create_account', tep_href_link(FILENAME_CREATE_ACCOUNT, '', 'SSL'), 'post', 'onSubmit="return check_form(create_account);"') . tep_draw_hidden_field('action', 'process'); ?> <div id="indicator"><?php echo tep_image(DIR_WS_IMAGES . 'indicator.gif'); ?></div>
<table border="0" width="100%" cellspacing="0" cellpadding="0" >

Line 418

Replace:

    <?php if ($process == true) {
      if ($entry_state_has_zones == true) {
        $zones_array = array();
        $zones_query = tep_db_query("select zone_name from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "' order by zone_name");
        while ($zones_values = tep_db_fetch_array($zones_query)) {
          $zones_array[] = array('id' => $zones_values['zone_name'], 'text' => $zones_values['zone_name']);
        }
        echo tep_draw_pull_down_menu('state', $zones_array);
      } else {
        echo tep_draw_input_field('state');
      }
    } else {
      echo tep_draw_input_field('state');
    }
   ?> 

with:

<div id="states">
<?php
// +Country-State Selector
echo ajax_get_zones_html($country,'',false);
// -Country-State Selector
?>
</div>

Line 442

Replace:

                <td class="main"><?php echo tep_get_country_list('country') . ' ' . (tep_not_null(ENTRY_COUNTRY_TEXT) ? '<span class="inputRequirement">' . ENTRY_COUNTRY_TEXT . '</span>': ''); ?></td>

with:

                      <?php // +Country-State Selector ?>
<td class="main" colspan="3"><?php echo tep_get_country_list('country',$country,'onChange="getStates(this.value, \'states\');"') . '&nbsp;' . (tep_not_null(ENTRY_COUNTRY_TEXT) ? '<span class="inputRequirement">' . ENTRY_COUNTRY_TEXT . '</span>': ''); ?></td>
<?php // -Country-State Selector ?>

At the very end of the page

Find:

<?php require(DIR_WS_INCLUDES . 'application_bottom.php'); ?>

Replace with:

<?php require(DIR_WS_INCLUDES . 'application_bottom.php'); ?>
<?php
// +Country-State Selector
}
// -Country-State Selector
?>

catalog/includes/languages/english.php

Insert anywhere convenient:

// +Country-State Selector
define ('DEFAULT_COUNTRY', '223');
// -Country-State Selector

Change 223 to the code for your default country. See the values in the database table countries for the appropriate codes. As an alternative, you could add this in catalog/includes/application_top.php.

You may also want to change the following definitions similar to the following:

define('ENTRY_STATE_TEXT', '* (Select country first)');
define('ENTRY_COUNTRY_TEXT', '* (State Dropdown will auto-update when changed)');

catalog/includes/modules/address_book_details.php

Line 28

Replace:

      <tr class="infoBoxContents">
<td><table border="0" cellspacing="2" cellpadding="2">

with:

      <tr class="infoBoxContents">
<td><table border="0" cellspacing="2" cellpadding="2">
<div id="indicator"><?php echo tep_image(DIR_WS_IMAGES . 'indicator.gif'); ?></div>

Line 97

Replace:

   <?php
    if ($process == true) {
      if ($entry_state_has_zones == true) {
        $zones_array = array();
        $zones_query = tep_db_query("select zone_name from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "' order by zone_name");
        while ($zones_values = tep_db_fetch_array($zones_query)) {
          $zones_array[] = array('id' => $zones_values['zone_name'], 'text' => $zones_values['zone_name']);
        }
        echo tep_draw_pull_down_menu('state', $zones_array);
      } else {
        echo tep_draw_input_field('state');
      }
    } else {
      echo tep_draw_input_field('state', tep_get_zone_name($entry['entry_country_id'], $entry['entry_zone_id'], $entry['entry_state']));
    }
   ?>

with:

<div id="states">
<?php
// +Country-State Selector
echo ajax_get_zones_html($entry['entry_country_id'],($entry['entry_zone_id']==0 ? $entry['entry_state'] : $entry['entry_zone_id']),false);
// -Country-State Selector
?>
</div>

Line 122

Replace:

            <td class="main"><?php echo tep_get_country_list('country', $entry['entry_country_id']) . ' ' . (tep_not_null(ENTRY_COUNTRY_TEXT) ? '<span class="inputRequirement">' . ENTRY_COUNTRY_TEXT . '</span>': ''); ?></td>

with:

			<?php // +Country-State Selector ?>
<td class="main"><?php echo tep_get_country_list('country', $entry['entry_country_id'],'onChange="getStates(this.value,\'states\');"') . '&nbsp;' . (tep_not_null(ENTRY_COUNTRY_TEXT) ? '<span class="inputRequirement">' . ENTRY_COUNTRY_TEXT . '</span>': ''); ?></td>
<?php // -Country-State Selector ?>

catalog/includes/modules/checkout_new_address.php

Line 15

Replace:

<table border="0" width="100%" cellspacing="0" cellpadding="2">

with:

<table border="0" width="100%" cellspacing="0" cellpadding="2">
<div id="indicator"><?php echo tep_image(DIR_WS_IMAGES . 'indicator.gif'); ?></div>

Line 80

Replace:

    <?php
    if ($process == true) {
      if ($entry_state_has_zones == true) {
        $zones_array = array();
        $zones_query = tep_db_query("select zone_name from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "' order by zone_name");
        while ($zones_values = tep_db_fetch_array($zones_query)) {
          $zones_array[] = array('id' => $zones_values['zone_name'], 'text' => $zones_values['zone_name']);
        }
        echo tep_draw_pull_down_menu('state', $zones_array);
      } else {
        echo tep_draw_input_field('state');
      }
    } else {
      echo tep_draw_input_field('state');
    }
   ?>

with:

<div id="states">
<?php
// +Country-State Selector
echo ajax_get_zones_html($country,'',false);
// -Country-State Selector
?>
</div>

Line 104

Replace:

    <td class="main"><?php echo tep_get_country_list('country') . ' ' . (tep_not_null(ENTRY_COUNTRY_TEXT) ? '<span class="inputRequirement">' . ENTRY_COUNTRY_TEXT . '</span>': ''); ?></td>

with:

				<?php // +Country-State Selector ?>
<td class="main"><?php echo tep_get_country_list('country',$country,'onChange="getStates(this.value,\'states\');"') . '&nbsp;' . (tep_not_null(ENTRY_COUNTRY_TEXT) ? '<span class="inputRequirement">' . ENTRY_COUNTRY_TEXT . '</span>': ''); ?></td>
<?php // -Country-State Selector ?>

catalog/admin/customers.php

After line 15:

 $action = (isset($HTTP_GET_VARS['action']) ? $HTTP_GET_VARS['action'] : '');

add:

 // +Country-State Selector
 $refresh = (isset($HTTP_POST_VARS['refresh']) ? $HTTP_POST_VARS['refresh'] : 'false');
 // -Country-State Selector

Line 43:

Replace:

        if (isset($HTTP_POST_VARS['entry_zone_id'])) $entry_zone_id = tep_db_prepare_input($HTTP_POST_VARS['entry_zone_id']);

with:

        // +Country-State Selector
        if (isset($HTTP_POST_VARS['entry_zone_id'])) {
           $entry_zone_id = tep_db_prepare_input($HTTP_POST_VARS['entry_zone_id']);
        } else {
           $entry_zone_id = 0;
        }
        if ($refresh != 'true') {
        // -Country-State Selector

Line 110:

Delete the following code:

        if (ACCOUNT_STATE == 'true') {
          if ($entry_country_error == true) {
            $entry_state_error = true;
          } else {
            $zone_id = 0;
            $entry_state_error = false;
            $check_query = tep_db_query("select count(*) as total from " . TABLE_ZONES . " where zone_country_id = '" . (int)$entry_country_id . "'");
            $check_value = tep_db_fetch_array($check_query);
            $entry_state_has_zones = ($check_value['total'] > 0);
            if ($entry_state_has_zones == true) {
              $zone_query = tep_db_query("select zone_id from " . TABLE_ZONES . " where zone_country_id = '" . (int)$entry_country_id . "' and zone_name = '" . tep_db_input($entry_state) . "'");
              if (tep_db_num_rows($zone_query) == 1) {
                $zone_values = tep_db_fetch_array($zone_query);
                $entry_zone_id = $zone_values['zone_id'];
              } else {
                $error = true;
                $entry_state_error = true;
              }
            } else {
              if ($entry_state == false) {
                $error = true;
                $entry_state_error = true;
              }
            }
         }
      }

Line 152:

Replace:

      if ($error == false) {

with:

// +Country-State Selector	  
      }  // End if (!$refresh)	
      if (($error == false) && ($refresh != 'true')) {
// -Country-State Selector

Line 196:

Replace:

          $processed = true;

with:

          $processed = true;
          // +Country-State Selector
          } else if ($refresh == 'true') {
            $cInfo = new objectInfo($HTTP_POST_VARS);
          }
          // -Country-State Selector

Line 238:

Replace:

  if ($action == 'edit' || $action == 'update') {

with:

  // +Country-State Selector
  if ($refresh == 'true') {
    $entry_state = '';
    $cInfo->entry_state = '';
  }
  // -Country-State Selector
  if ($action == 'edit' || $action == 'update') {

Line 333:

Replace:

//--></script>

with:

function refresh_form(form_name) {
   form_name.refresh.value = 'true';
   form_name.submit();
   return true;
   }
//--></script>

Line 369:

Replace:

      <tr><?php echo tep_draw_form('customers', FILENAME_CUSTOMERS, tep_get_all_get_params(array('action')) . 'action=update', 'post', 'onSubmit="return check_form();"') . tep_draw_hidden_field('default_address_id', $cInfo->customers_default_address_id); ?>

with:

      <tr><?php echo tep_draw_form('customers', FILENAME_CUSTOMERS, tep_get_all_get_params(array('action')) . 'action=update', 'post', 'onSubmit="return check_form();"') . tep_draw_hidden_field('default_address_id', $cInfo->customers_default_address_id); ?>
         <?php
         // +Country-State Selector
         echo tep_draw_hidden_field('refresh','false'); 
         // -Country-State Selector
         ?>

Line 581:

Replace:

    $entry_state = tep_get_zone_name($cInfo->entry_country_id, $cInfo->entry_zone_id, $cInfo->entry_state);
    if ($error == true) {
      if ($entry_state_error == true) {
        if ($entry_state_has_zones == true) {
          $zones_array = array();
          $zones_query = tep_db_query("select zone_name from " . TABLE_ZONES . " where zone_country_id = '" . tep_db_input($cInfo->entry_country_id) . "' order by zone_name");
          while ($zones_values = tep_db_fetch_array($zones_query)) {
            $zones_array[] = array('id' => $zones_values['zone_name'], 'text' => $zones_values['zone_name']);
          }
          echo tep_draw_pull_down_menu('entry_state', $zones_array) . ' ' . ENTRY_STATE_ERROR;
        } else {
          echo tep_draw_input_field('entry_state', tep_get_zone_name($cInfo->entry_country_id, $cInfo->entry_zone_id, $cInfo->entry_state)) . ' ' . ENTRY_STATE_ERROR;
        }
      } else {
        echo $entry_state . tep_draw_hidden_field('entry_zone_id') . tep_draw_hidden_field('entry_state');
      }
    } else {
      echo tep_draw_input_field('entry_state', tep_get_zone_name($cInfo->entry_country_id, $cInfo->entry_zone_id, $cInfo->entry_state));
    }

with:

     // +Country-State Selector
     $entry_state = tep_get_zone_name($cInfo->entry_country_id, $cInfo->entry_zone_id, $cInfo->entry_state);
     $zones_array = array();
     $zones_query = tep_db_query("select zone_name, zone_id from " . TABLE_ZONES . " where zone_country_id = '" . (int)$cInfo->entry_country_id . "' order by zone_name");
     while ($zones_values = tep_db_fetch_array($zones_query)) {
        $zones_array[] = array('id' => $zones_values['zone_id'], 'text' => $zones_values['zone_name']);
     }
       if (count($zones_array) > 0) {
         echo tep_draw_pull_down_menu('entry_zone_id', $zones_array, $cInfo->entry_zone_id);
         echo tep_draw_hidden_field('entry_state', '');
      } else {
         echo tep_draw_input_field('entry_state', $entry_state);
      }
      // -Country-State Selector

Line 610:

Replace:

  if ($error == true) {
    if ($entry_country_error == true) {
      echo tep_draw_pull_down_menu('entry_country_id', tep_get_countries(), $cInfo->entry_country_id) . ' ' . ENTRY_COUNTRY_ERROR;
    } else {
      echo tep_get_country_name($cInfo->entry_country_id) . tep_draw_hidden_field('entry_country_id');
    }
  } else {
    echo tep_draw_pull_down_menu('entry_country_id', tep_get_countries(), $cInfo->entry_country_id);
  }

with:

// +Country-State Selector
echo css_get_country_list('entry_country_id',  $cInfo->entry_country_id,'onChange="return refresh_form(customers);"');
// -Country-State Selector

catalog/admin/includes/functions/html_output.php

Insert before the closing ?>:

// +Country-State Selector
// Adapted from functions in catalog/includes/general.php and html_output.php for Country-State Selector
// Returns an array with countries
// TABLES: countries
  function css_get_countries($countries_id = '', $with_iso_codes = false) {
    $countries_array = array();
    if (tep_not_null($countries_id)) {
      if ($with_iso_codes == true) {
        $countries = tep_db_query("select countries_name, countries_iso_code_2, countries_iso_code_3 from " . TABLE_COUNTRIES . " where countries_id = '" . (int)$countries_id . "' order by countries_name");
        $countries_values = tep_db_fetch_array($countries);
        $countries_array = array('countries_name' => $countries_values['countries_name'],
                                 'countries_iso_code_2' => $countries_values['countries_iso_code_2'],
                                 'countries_iso_code_3' => $countries_values['countries_iso_code_3']);
      } else {
        $countries = tep_db_query("select countries_name from " . TABLE_COUNTRIES . " where countries_id = '" . (int)$countries_id . "'");
        $countries_values = tep_db_fetch_array($countries);
        $countries_array = array('countries_name' => $countries_values['countries_name']);
      }
    } else {
      $countries = tep_db_query("select countries_id, countries_name from " . TABLE_COUNTRIES . " order by countries_name");
      while ($countries_values = tep_db_fetch_array($countries)) {
        $countries_array[] = array('countries_id' => $countries_values['countries_id'],
                                   'countries_name' => $countries_values['countries_name']);
      }
    }

    return $countries_array;
  }

////
// Creates a pull-down list of countries
  function css_get_country_list($name, $selected = '', $parameters = '') {
    $countries_array = array();

    $countries = css_get_countries();

    for ($i=0, $n=sizeof($countries); $i<$n; $i++) {
      $countries_array[] = array('id' => $countries[$i]['countries_id'], 'text' => $countries[$i]['countries_name']);
    }

    return tep_draw_pull_down_menu($name, $countries_array, $selected, $parameters);
  }
 // -Country-State Selector 

Support

For support, please go to http://forums.oscommerce.com/index.php?showtopic=88987

Want to show your appreciation for this contribution? Please consider making a (US) tax-deductible contribution to Kitty Angels, a no-kill cat shelter. See the How to Help link on the Kitty Angels site for details. The author of this contribution is a Kitty Angels volunteer and runs their web site. Alternatively, you can make a PayPal contribution to steve@stevelionel.com and mention Country-State Selector. All donations will be passed on to Kitty Angels.

License

This software is licensed under the GNU General Public License. See the accompanying LICENSE.TXT for details.

Change History

1.0.1 - April 21, 2004
Add (int) casts on country code in SQL queries. Fix address book bugs. Add note about moving DEFAULT_COUNTRY define to application_top.php. Fix format of code in this document so that it can be copied and pasted properly.
1.0.2 - April 21, 2004
Add missing line from the instructions.
1.0.3 - April 22, 2004
More HTML copy/paste fixes, and cleaned up the formatting. No code changes.
1.0.4 - April 27, 2004
Add support for changing addresses during checkout. Newly modified files are catalog/checkout_payment_address.php, catalog/checkout_shipping_address.php and catalog/includes/modules/checkout_new_address.php. Other files were not modified in this edit.
1.0.5 - May 1, 2004
Add missing change for includes/modules/checkout_new_address.php to the instructions (was in the included file).
1.0.6 - December 2, 2004
In the supplied pre-edited copy of checkout_shipping_address.php, some important code was missing. The editing instructons in this readme are correct. That file was updated in the ZIP. No other changes.
1.1.0 - January 1, 2005
Move clearing of the state value to the form and out of form_check.js.php, for better cross-browser compatibility. In create_account.php, set focus to state field on refresh. Changes from 1.0.6 are: address_book_process line 252 (add), create_account line 19 (changed), create_account line 252 (changed), create_account line 263 (add), form_check.js.php (one line deleted).
1.1.1 - March 2, 2005
Corrected typo in <body> tags in this readme.
1.2.0 - April 24, 2005
Reworked create_account code to allow for a zone to be a substring of another zone (other pages already did this). Minor bug fixes. Edits include: address_book_process.php (55), create_account.php (130, 418), checkout_new_address.php (80)
1.2.1 - May 31, 2005
Add missing line if ($HTTP_POST_VARS['action'] == 'refresh') {$state = '';} in checkout_payment_address.php and checkout_shipping_address.php.
1.3.0 - August 21, 2005
Added support for Admin..Customers..Edit and for those using the Purchase Without Account contribution. No other changes.
1.3.1 - September 4, 2005
Remove "onload" code that does a setfocus on the state field in create_account.php and address_book_process.php, as this causes a JavaScript warning. The body tag in both of these files should revert to the standard <body marginwidth="0" marginheight="0" topmargin="0" bottommargin="0" leftmargin="0" rightmargin="0"> Add some debugging tips.
1.3.2 - September 10, 2005
Minor bug fixes to PWA merge of Order_Info.php only.
1.3.3 - December 9, 2005
Remove PWA merge files as those are now in the PWA contribution. Merged in the edits from the osCommerce 2.2-MS2 051113 update for the replacement files provided (catalog/address_book_process.php and catalog/admin/includes/functions/html_output.php). None of those edits are in code changed by this contribution, so if you've already made those edits, or don't want to, there's nothing new here.
1.4.0 - February 17, 2008
Fix bug where for the checkout_payment_address and checkout_shipping_address pages, it would not accept a state/zone that was an initial substring of another. For countries with zones, require customer to select a zone. In the dropdown list of states, add the "zone code" (state abbreviation) in parentheses. Update the instructions and pre-edited files to reflect osCommerce 2.2-RC2a. Add instructions for enabling use with single country.
1.4.1 - February 28, 2008
The instructions for 1.4.0 missed adding "zone_code" to the query in the edit for line 418 of create_account.php. The pre-edited file already had the edit.
1.5.0 - April 11, 2008
Added AJAX routines for updates without page refresh
1.5.1 - April 12, 2008
Required ajax.php file was added inside an if statement and should have been outside of the if statement
1.5.2 - April 20, 2008
Default Zone code added
1.5.3 - April 20, 2008
Default State (not Default Zone) was not added in 1.5.2 (For countries in the database without any defined zones)
1.5.4 - April 20, 2008
Added missing condition statement to catalog/address_book_process.php
1.5.5 - April 21, 2008
Added ability to have multiple country-state dropdowns per page with each updated independently. Updated so that form name is of no relevance.