--- salesforce_api.module	25 Feb 2009 23:46:02 -0000	1.2.2.3
+++ salesforce_api.module	16 Mar 2009 22:18:55 -0000
@@ -19,8 +19,9 @@ define('SALESFORCE_DIR_WSDL', SALESFORCE
 
 // Define Drupal paths for various parts of the Salesforce UI.
 define('SALESFORCE_PATH_ADMIN', 'admin/settings/salesforce');
-define('SALESFORCE_PATH_FIELDMAPS', 'admin/settings/salesforce/fieldmap');
-define('SALESFORCE_PATH_DEMO', 'admin/settings/salesforce/demo');
+define('SALESFORCE_PATH_FIELDMAPS', SALESFORCE_PATH_ADMIN.'/fieldmap');
+define('SALESFORCE_PATH_DEMO', SALESFORCE_PATH_ADMIN.'/demo');
+define('SALESFORCE_PATH_OBJECT', SALESFORCE_PATH_ADMIN.'/object');
 
 // Define field importing requirements.
 // TODO: I believe we need to use bitwise operators instead.
@@ -111,6 +112,25 @@ function salesforce_api_menu() {
     'type' => MENU_CALLBACK,
     'file' => 'salesforce_api.admin.inc',
   );
+  $items[SALESFORCE_PATH_OBJECT] = array(
+    'title' => 'Object setup',
+    'description' => 'Define which SalesForce objects you would like to be available in your Drupal site.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('salesforce_api_admin_object'),
+    'access arguments' => array('administer salesforce'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'salesforce_api.admin.inc',
+    );
+  
+  $items[SALESFORCE_PATH_OBJECT.'/%'] = array(
+    'title' => 'Object setup',
+    'page callbacck' => 'drupal_get_form',
+    'page arguments' => array('salesforce_api_admin_object_settings', 
+                              count(explode('/', SALESFORCE_PATH_OBJECT))),
+    'access arguments' => array('administer salesforce'),
+    'type' => MENU_CALLBACK,
+    'file' => 'salesforce_api.admin.inc',
+    );
 
   return $items;
 }
@@ -734,6 +754,35 @@ function salesforce_api_id_save($drupal_
 }
 
 /**
+ * Load and return an array of all the local SF object types from the salesforce_object cache.
+ *
+ * @param array $options :
+ *  valid keys are:
+ *    boolean 'full' => if true, return fully-loaded SF types, decoded and unserialized. 
+ *      otherwise, return "name"s and "valid"s only
+ *    string 'conditions' => SQL WHERE clause to add to the query (e.g. 'valid <> 0')
+ * @return an array of SF types, indexed on type names
+**/
+function salesforce_api_get_types($options = array()) {
+  $options = array_merge(array(
+    'full' => TRUE,
+    'conditions' => NULL,
+    ), $options);
+  $fields = $options['full'] ? '*' : 'name, valid';
+  $where = $options['conditions'] ? 'WHERE ' . $options['conditions'] : null;
+  $sql = "SELECT $fields FROM {salesforce_objects} $where";
+  $result = db_query($sql);
+  $types = array();
+  while($row = db_fetch_array($result)) {
+    if(isset($row['cache'])) {
+      $row['cache'] = unserialize(base64_decode($row['cache']));
+    }
+    $types[$row['name']] = $row;
+  }
+  return $types;
+}
+
+/**
  * Implementation of hook_theme()
  *
  * Registers theme callback for admin screen
@@ -743,5 +792,157 @@ function salesforce_api_theme($existing,
     'salesforce_api_fieldmap_edit_form_table' => array(
       'arguments' => array('form' => NULL),
     ),
+    'salesforce_api_object_options' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'salesforce_api_error' => array(
+      'arguments' => array('type' => NULL, 'message' => NULL),
+    ),
   );
-}
\ No newline at end of file
+}
+
+/**
+ * Wrapper for SOAP SforceBaseClient::describeGlobal
+ * @return an SFQueryResult object (look at ->types for an array of SF object types)
+**/
+function salesforce_api_describeGlobal() {
+  static $objects;
+  if (!empty($objects)) {
+    return $objects;
+  }
+  ob_start('salesforce_api_soapfault_handler');
+	$sf = salesforce_api_connect();
+	if ($sf === FALSE) {
+		$link = l('Please verify that you have completed your SalesForce credentials', SALESFORCE_PATH_ADMIN);
+		drupal_set_message(t('Unable to connect to SalesForce. !link', array('!link' => $link)), 'error');
+    ob_end_clean();
+		return;
+	}
+  $objects = $sf->client->describeGlobal();
+  ob_end_clean();
+  return $objects;
+}
+
+/**
+ * Wrapper for SOAP SforceBaseClient::describeSObject
+ * Given an sf object type, return the SF Object definition
+ * @param string type : the machine-readable name of the SF object type
+**/
+function salesforce_api_describeSObject($type) {
+  if (!is_string($type)) {
+    drupal_set_message(t('DescribeSObject expects a string. ' . gettype($type) . ' received.'), 'error');
+    return false;
+  }
+  
+  $objects = salesforce_api_describeSObjects($type);
+  if(!empty($objects[$type])) {
+    return $objects[$type];
+  } else {
+    drupal_set_message(t('DescribeSObject failed to find ' . $type), 'error');
+    return false;
+  }
+}
+
+/**
+ * Wrapper for SOAP SforceBaseClient::describeSObjects
+ * Given an array of sf object type, return an array of SF object definitions
+ * @param array types : an array of machine-readable names of the SF object types
+**/
+function salesforce_api_describeSObjects($types) {
+  static $objects;
+  if(is_string($types)) {
+    $types = array($types);
+  }
+  if(!is_array($types)) {
+    drupal_set_message(t('DescribeSObjects expects an array. ' . gettype($types) . ' received.'), 'error');
+    return false;
+  }
+  
+  //if we've already got everything cached, return the cached versions
+  if(!empty($objects)) {
+    $outstanding = array_diff($types, array_keys($objects));
+    if(empty($outstanding)) {
+      $ret = array();
+      foreach ($types as $k) {
+        $ret[$k] = $cache[$k];
+      }
+      return $ret;
+    }
+  }
+  
+  if (is_string($types)) {
+    $types = array($types);
+  }
+
+  ob_start('salesforce_api_soapfault_handler');
+	$sf = salesforce_api_connect();
+	if ($sf === FALSE) { 
+		$link = l('Please verify that you have completed your SalesForce credentials', SALESFORCE_PATH_ADMIN);
+		drupal_set_message(t('Unable to connect to SalesForce. !link', array('!link' => $link)), 'error');
+    ob_end_clean();
+		return;
+	}
+  $objects = $sf->client->describeSObjects($types);
+  ob_end_clean();
+  
+  if(empty($objects)) {
+    return array();
+  }
+  if (is_object($objects)) {
+    $objects = array($objects);
+  }
+  $tmp = array();
+  foreach ($objects as $o) {
+    $tmp[$o->name] = $o;
+  }
+  $objects = $tmp;
+  return $objects;
+}
+
+
+// =========================================================================
+// =                   ERROR HANDLING                                      =
+// =========================================================================
+/**
+ * This is our ob_start callback / error handler for SF calls.
+ * Sometimes SoapClient throws a fatal E_ERROR.
+ * Sometimes we don't want the user to receive a WSOD.
+ * Sometimes we have to work around the default error handling.
+**/
+function salesforce_api_soapfault_handler() {
+  $isError = false;
+  if ($error = error_get_last()){
+    switch($error['type']){
+      case E_ERROR:
+      case E_CORE_ERROR:
+      case E_COMPILE_ERROR:
+      case E_USER_ERROR:
+      $isError = true;
+      break;
+    }
+  }
+  if ($isError && strpos($error['message'], 'SoapFault') !== FALSE) {
+    salesforce_api_nice_error();
+  }
+}
+/**
+ * Display a nice, themed error message instead of WSOD or PHP error message 
+**/
+function salesforce_api_nice_error() {
+  echo theme('page', theme('salesforce_api_error'));
+  exit;
+}
+/**
+ * Theme an error message. Standardize messages for similar types of errors.
+ *
+ * @param string $type : what type of error is it?
+ * @param string $message : gets t()'d along w/ any default message for $type
+ * @see salesforce_api_soapfault_handler
+**/
+function theme_salesforce_api_error($type = null, $message = null) {
+  switch($type) {
+    default:
+      return '<p>An internal error has occurred. Please try again later.</p>'.t($message);
+  }
+}
+
