Index: uc_taxes/uc_taxes.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/uc_taxes/uc_taxes.admin.inc,v
retrieving revision 1.1.2.11
diff -u -p -r1.1.2.11 uc_taxes.admin.inc
--- uc_taxes/uc_taxes.admin.inc	18 Mar 2010 03:09:42 -0000	1.1.2.11
+++ uc_taxes/uc_taxes.admin.inc	16 Aug 2010 08:36:24 -0000
@@ -24,6 +24,12 @@ function uc_taxes_admin_settings() {
        l(t('delete'), 'admin/store/settings/taxes/'. $rate_id .'/delete'),
     );
 
+    if (module_exists('exportables') && module_exists('ctools')) {
+      if (exportables_api_version('2.0')) {    
+        $ops[] = l(t('export'), 'admin/store/settings/taxes/'. $rate_id .'/export');
+      }
+    }
+
     // Add the row to the table.
     $rows[] = array(
       check_plain($rate->name),
@@ -74,6 +80,19 @@ function uc_taxes_form($form_state, $rat
     '#value' => $rate_id,
   );
 
+  if (module_exists('exportables') && module_exists('ctools')) {
+    if (exportables_api_version('2.0')) {    
+      exportables_machine_get('uc_taxes', $rate);
+      $form['machine_name'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Machine name'),
+        '#description' => t('Machine name for the tax rate.'),
+        '#default_value' => $rate_id ? $rate->machine : '',
+        '#required' => TRUE,
+      );
+    }
+  }  
+
   $form['name'] = array(
     '#type' => 'textfield',
     '#title' => t('Name'),
@@ -172,22 +191,26 @@ function uc_taxes_form_submit($form, &$f
   }
 
   // Build the rate object based on the form values and save it.
-  $rate = array(
-    'id' => $form_state['values']['id'],
-    'name' => $form_state['values']['name'],
-    'rate' => $form_state['values']['rate'],
-    'taxed_product_types' => array_filter($form_state['values']['taxed_product_types']),
-    'taxed_line_items' => array_filter($form_state['values']['taxed_line_items']),
-    'weight' => $form_state['values']['weight'],
-    'shippable' => $form_state['values']['shippable'],
-  );
-  uc_taxes_rate_save((object) $rate);
+  $rate = new stdClass;
+  $rate->id = $form_state['values']['id'];
+  $rate->machine = $form_state['values']['machine_name'];
+  $rate->name = $form_state['values']['name'];
+  $rate->rate = $form_state['values']['rate'];
+  $rate->taxed_product_types = array_filter($form_state['values']['taxed_product_types']);
+  $rate->taxed_line_items = array_filter($form_state['values']['taxed_line_items']);
+  $rate->weight = $form_state['values']['weight'];
+  $rate->shippable = $form_state['values']['shippable'];
+  $new = TRUE;
+  if ($rate->id) {
+    $new = FALSE;
+  }
+  uc_taxes_rate_save($rate, $new);
 
   // Update the name of the associated predicate.
-  db_query("UPDATE {ca_predicates} SET title = '%s' WHERE pid = '%s'", $rate['name'], 'uc_taxes_'. $rate['id']);
+  db_query("UPDATE {ca_predicates} SET title = '%s' WHERE pid = '%s'", $rate->name, 'uc_taxes_'. $rate->id);
 
   // Display a message and redirect back to the overview.
-  drupal_set_message(t('Tax rate %name saved.', array('%name' => $form_state['values']['name'])));
+  drupal_set_message(t('Tax rate %name saved.', array('%name' => $rate->name)));
 
   $form_state['redirect'] = 'admin/store/settings/taxes';
 }
@@ -202,10 +225,10 @@ function uc_taxes_clone($rate_id) {
 
   // Tweak the name and unset the rate ID.
   $rate->name = t('Copy of !name', array('!name' => $rate->name));
-  $rate->id = NULL;
+  unset($rate->id);
 
   // Save the new rate.
-  $rate = uc_taxes_rate_save($rate);
+  uc_taxes_rate_save($rate);
 
   // Clone the associated predicate as well.
   if ($predicate = ca_load_predicate('uc_taxes_'. $rate_id)) {
Index: uc_taxes/uc_taxes.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/uc_taxes/uc_taxes.install,v
retrieving revision 1.10.2.9
diff -u -p -r1.10.2.9 uc_taxes.install
--- uc_taxes/uc_taxes.install	19 Mar 2010 21:27:18 -0000	1.10.2.9
+++ uc_taxes/uc_taxes.install	16 Aug 2010 08:36:24 -0000
@@ -14,12 +14,42 @@ function uc_taxes_schema() {
 
   $schema['uc_taxes'] = array(
     'description' => 'Stores tax information.',
+    'join' => array(
+      'exportables' => array(
+        'table' => 'exportables',
+        'left_key' => 'id',
+        'right_key' => 'id',
+        'extra' => "AND t__1.type = 'uc_taxes'",
+        'load' => array(
+          'machine',
+        ),
+      ),    
+    ),
+    'export' => array(     
+      'key' => 'machine',
+      'can disable' => FALSE,
+      'key in table' => 'exportables',
+      'identifier' => 'rate',
+      'default hook' => 'uc_taxes',
+      'api' => array(
+        'name' => 'uc_taxes',
+        'owner' => 'uc_taxes',
+        'api' => 'uc_taxes',
+        'minimum_version' => 1,
+        'current_version' => 1,
+      ),
+      'delete callback' => 'uc_taxes_rate_delete',
+      'to hook code callback' => 'uc_taxes_to_hook_code',
+      'list callback' => 'uc_taxes_list_callback',
+      'load all callback' => 'uc_taxes_load_all',      
+    ),
     'fields' => array(
       'id' => array(
         'description' => 'Primary key: Unique tax rate id.',
         'type' => 'serial',
         'unsigned' => TRUE,
         'not null' => TRUE,
+        'no export' => TRUE,
       ),
       'name' => array(
         'description' => 'The tax rate name.',
Index: uc_taxes/uc_taxes.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/uc_taxes/uc_taxes.module,v
retrieving revision 1.12.2.20
diff -u -p -r1.12.2.20 uc_taxes.module
--- uc_taxes/uc_taxes.module	27 Jan 2010 22:23:32 -0000	1.12.2.20
+++ uc_taxes/uc_taxes.module	16 Aug 2010 08:36:25 -0000
@@ -92,6 +92,27 @@ function uc_taxes_menu() {
     'type' => MENU_CALLBACK,
   );
 
+  if (module_exists('exportables') && module_exists('ctools')) {
+    if (exportables_api_version('2.0')) {
+      $items['admin/store/settings/taxes/%/export'] = array(
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array('uc_taxes_export_rates', 4),
+        'access arguments' => array('configure taxes'),
+        'type' => MENU_CALLBACK,
+        'file' => 'uc_taxes.export.inc',
+      );
+      $items['admin/store/settings/taxes/import'] = array(
+        'title' => 'Import tax rate',
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array('uc_taxes_import_rates'),
+        'access arguments' => array('configure taxes'),
+        'weight' => 6,
+        'type' => MENU_LOCAL_TASK,
+        'file' => 'uc_taxes.export.inc',
+      );
+    }
+  }
+
   return $items;
 }
 
@@ -297,17 +318,21 @@ function uc_line_item_tax_subtotal($op, 
  * @return
  *   The saved tax rate object including the rate ID for new rates.
  */
-function uc_taxes_rate_save($rate) {
-  // Save it as a new rate if no ID is specified.
-  if (!$rate->id) {
+function uc_taxes_rate_save(&$rate, $new = TRUE) {
+  if ($new) {
     drupal_write_record('uc_taxes', $rate);
   }
-  // Otherwise update the existing tax rate's data.
   else {
     drupal_write_record('uc_taxes', $rate, array('id'));
   }
+  cache_clear_all('uc_taxes:rates', 'cache');
+  if ($new) {
+    // A new tax rate was created, inform this to exportables.
+    exportables_machine_save('uc_taxes', $rate, $rate->machine);
+  }
 
-  return $rate;
+  // Finally, set the value of in_code to 1, to prevent CTools load it from the database.
+  db_query("UPDATE {exportables} SET in_code = 1 WHERE machine = '%s' AND type = 'uc_taxes'", $rate->machine);  
 }
 
 /**
@@ -315,36 +340,71 @@ function uc_taxes_rate_save($rate) {
  *
  * @param $rate_id
  *   The ID of the specific rate to load or NULL to return all available rates.
+ * @param $reset
+ *   Whether or not to skip cache load.
  * @return
  *   An object representing the requested tax rate or an array of all tax rates
  *     keyed by rate ID.
  */
-function uc_taxes_rate_load($rate_id = NULL) {
-  static $rates = array();
-
-  // If the rates have not been cached yet...
-  if (empty($rates)) {
-    // Get all the rate data from the database.
-    $result = db_query("SELECT * FROM {uc_taxes} ORDER BY weight");
-
-    // Loop through each returned row.
-    while ($rate = db_fetch_object($result)) {
-      // Unserialize some arrays and cache the rate in a static array.
-      $rate->taxed_product_types = unserialize($rate->taxed_product_types);
-      $rate->taxed_line_items = unserialize($rate->taxed_line_items);
-
-      $rates[$rate->id] = $rate;
+function uc_taxes_rate_load($rate_id = NULL, $reset = TRUE) {
+  static $rates;
+  if ($reset || !isset($rates)) {
+    $rates = array();
+    if (!$reset && ($cache = cache_get('uc_taxes:rates', 'cache')) && is_array($cache->data)) {
+      $rates = $cache->data;
+      // Return a rate as specified.
+      if ($rate_id) {
+        return $rates[$rate_id];
+      }
+      return $rates;
+    }
+    if (module_exists('exportables') && module_exists('ctools')) {
+      if (exportables_api_version('2.0') && ctools_api_version('1.7')) {
+        ctools_include('export');
+        ctools_export_load_object_reset('uc_taxes');
+        $default_rates = ctools_export_load_object('uc_taxes');
+        $default_rates_exportables = exportables_load_all('uc_taxes');
+        foreach ($default_rates as $machine_name => $default_rate) {
+          if (isset($default_rates_exportables[$machine_name])) {
+            $rates[$default_rate->id] = $default_rates_exportables[$machine_name];
+          }
+          if($default_rate->in_code_only && empty($rates[$default_rate->id])) {
+            uc_taxes_rate_save($default_rate);
+          }
+          elseif(!$default_rate->in_code_only) {
+            $rates = uc_taxes_rate_load_from_db();            
+          }
+          if (!$rates[$default_rate->id]->name) {
+            unset($rates[$default_rate->id]);
+          }
+        }             
+      }    
+    }
+    else {
+      $rates = uc_taxes_rate_load_from_db();
     }
+    cache_set('uc_taxes:rates', $rates);        
   }
-
   // Return a rate as specified.
   if ($rate_id) {
     return $rates[$rate_id];
   }
-  // Or return the whole shebang.
-  else {
-    return $rates;
+  return $rates;
+}
+
+/**
+ * Load taxes from db
+ */
+function uc_taxes_rate_load_from_db() {
+  $rates = array();
+  $result = db_query("SELECT * FROM {uc_taxes} ORDER BY weight");
+  while ($rate = db_fetch_object($result)) {
+    // Unserialize some arrays and cache the rate in a static array.
+    $rate->taxed_product_types = unserialize($rate->taxed_product_types);
+    $rate->taxed_line_items = unserialize($rate->taxed_line_items);
+    $rates[$rate->id] = $rate;
   }
+  return $rates;
 }
 
 /**
@@ -359,6 +419,7 @@ function uc_taxes_rate_delete($rate_id) 
 
   // Delete the associated predicated if it has been saved to the database.
   ca_delete_predicate('uc_taxes_'. $rate_id);
+  cache_clear_all('uc_taxes:rates', 'cache');  
 }
 
 /**
@@ -555,3 +616,179 @@ function uc_taxes_apply_tax($order, $tax
     return $line_item;
   }
 }
+
+/**
+ * Implementation of hook_features_export_options().
+ */
+function uc_taxes_features_export_options() {
+  ctools_include('export');
+  $rates = ctools_export_load_object('uc_taxes', 'all');
+  if (is_array($rates)) {
+    foreach ($rates as $id => $rate) {
+      $rates_export[$id] = t('@tax tax', array('@tax' => $rate->name));
+    }
+  }
+  return $rates_export;
+}
+
+/**
+ * Implementation of hook_features_export().
+ */
+function uc_taxes_features_export($data, &$export, $module_name = '') {
+  $export['dependencies']['exportables'] = 'exportables'; 
+  $return = ctools_component_features_export('uc_taxes', $data, $export, $module_name);
+  return $return;
+}
+
+/**
+ * Implementation of hook_FORM_ID_alter().
+ *
+ * Sync names every time admin/build/modules is rendered.
+ */
+function uc_taxes_form_system_modules_alter(&$form, $form_state) {
+  uc_taxes_exportables_sync_names();
+}
+
+/**
+ * Implementation of hook_FORM_ID_alter().
+ *
+ * Sync names every time admin/build/features is rendered.
+ */
+function uc_taxes_form_features_admin_form_alter(&$form, $form_state) {
+  uc_taxes_exportables_sync_names();
+}
+
+/**
+ * Implementation of hook_FORM_ID_alter().
+ */
+function uc_taxes_form_exportables_submit(&$form, $form_state) {
+  uc_taxes_exportables_sync_names();
+}
+
+/**
+ * Implementation of hook_FORM_ID_alter().
+ *
+ * Includes a machine name into the admin/settings/filter/ID form.
+ */
+function uc_taxes_form_uc_taxes_form_alter(&$form, $form_state) {
+  $form['#submit'][] = 'uc_taxes_form_exportables_submit';  
+}
+
+/**
+ * Implementation of hook_exportables().
+ */
+function uc_taxes_exportables() {
+  $exportables = array();
+  $exportables['uc_taxes'] = array(
+    'load all function' => 'uc_taxes_load_all',
+    'id property' => 'id',
+    'title property' => 'name',
+    'machine name property' => 'machine',
+  );
+  return $exportables;
+}
+
+/**
+ * Exportable Sync.
+ */
+function uc_taxes_exportables_sync_names() {
+  if (module_exists('exportables') && module_exists('ctools')) {
+    if (exportables_api_version('2')) {
+      exportables_sync_names('uc_taxes');
+    }
+  }
+}
+
+/**
+ * Implementation of hook_features_api().
+ *
+ * Provide features integration only when ctools and exportables are 
+ * installed.
+ */
+function uc_taxes_features_api() {
+  if (module_exists('exportables') && module_exists('ctools')) {
+    module_load_include('inc', 'features', 'includes/features.ctools');
+    return ctools_component_features_api('uc_taxes');
+  }
+}
+
+/**
+ * Implementation of hook_features_revert().
+ *
+ * Workaround until features support ctools 1.7 API to revert objects.
+ */
+function uc_taxes_features_revert($module) {
+  ctools_include('export');
+  $schema = ctools_export_get_schema('uc_taxes');
+  $export = $schema['export'];
+  $defaults = _ctools_export_get_defaults('uc_taxes', $export);
+  $objects = ctools_export_crud_load_all('uc_taxes', TRUE);
+
+  foreach ($defaults as $key => $default) {
+    if (isset($default->export_module) && $default->export_module == $module) {
+      if ($objects[$key]->type == t('Overridden')) {
+        ctools_export_crud_delete('uc_taxes', $objects[$key]);
+      }
+    }
+  }
+}
+
+/**
+ * List callback used by CTools to load all the available rates.
+ */
+function uc_taxes_list_callback() {
+  return uc_taxes_rate_load(NULL, TRUE);
+}
+
+/**
+ * Load all callback used by CTools to load all the available rates.
+ */
+function uc_taxes_load_all() {
+  return uc_taxes_rate_load(NULL, FALSE);
+}
+
+/**
+ * Load a set of taxes.
+ *
+ * @param $names
+ *   An array of tax rates.
+ *
+ * @return
+ *   An array of tax rate objects.
+ */
+function uc_taxes_load_multiple($names) {
+  $return = array();
+  $rates = uc_taxes_load_all();
+  foreach ($rates as $rate) {
+    exportables_machine_get('uc_taxes', $rate);
+    if (in_array($rate->machine, $names)) {
+      $return[$rate->machine] = $rate;
+    }
+  }
+  return $return;
+}
+
+/**
+ * Callback function used by ctools and features to generate the code
+ * for the tax rate when a feature is created.
+ */
+function uc_taxes_to_hook_code($names, $module) {
+  $output = '';
+  $objects = NULL;
+  $objects = uc_taxes_load_multiple($names);
+  if (count($objects)) {
+    $output = "/**\n";
+    $output .= " * Export of uc_taxes\n";
+    $output .= " */\n";
+    $output .= "function " . $module . "_uc_taxes() {\n";
+    $output .= "  \$rates = array();\n\n";
+    foreach ($objects as $object) {
+      $output .= ctools_export_object('uc_taxes', $object, '  ');
+      $output .= "  \$rates['" . check_plain($object->machine) . "'] = \$rate;\n\n";
+    }
+    $output .= "  return \$rates;\n";
+    $output .= "}\n";
+  }
+
+  return $output;
+}
