Hoping this will assist someone new to the API with a practical implementation.
Library
Code: Select all
<?php
class BullhornAPI {
const API_USERNAME = '[created via support ticket]';
const API_PASSWORD = '[set in admin]';
const CLIENT_ID = '[created via support ticket]';
const CLIENT_SECRET = '[created via support ticket]';
const ENDPOINT_AUTH_CODE = 'https://auth.bullhornstaffing.com/oauth/authorize?%s';
const ENDPOINT_ACCESS_TOKEN = 'https://rest-west.bullhornstaffing.com/oauth/token?%s';
const ENDPOINT_REST_TOKEN = 'https://rest.bullhornstaffing.com/rest-services/login?version=*&access_token=%s';
const PUBLIC_ERROR = 'We\'re unable to process your application at this time. Contact a site administrator to learn more.';
const RESUME_FILE_TYPE = 'Resume'; # Must exist in BH account
const REWIND_CREATE_ON_RESUME_FAIL = TRUE;
const SHOW_LOG = FALSE;
private $access_token;
private $auth_code;
private $comm_response;
private $comm_response_info;
private $errors = array();
private $is_dev = FALSE;
private $refresh_token;
private $rest_token;
private $rest_endpoint_url;
public function __construct() {
if(function_exists('is_dev')) :
$this->is_dev = (bool) is_dev();
endif;
}
public function candidateAttachResume($candidate_id, $resume_path=NULL, $resume_filename=NULL) {
if(TRUE !== $this->oAuth()) :
return FALSE;
endif;
if(in_array(self::RESUME_FILE_TYPE, array(NULL, ''))) :
$this->errorsSet('Empty resume file type.', __LINE__, __METHOD__);
return FALSE;
endif;
if(NULL === $resume_path) :
$this->errorsSet('Empty resume path.', __LINE__, __METHOD__);
return FALSE;
endif;
if(!is_readable($resume_path)) :
$this->errorsSet('Unable to locate resume.', __LINE__, __METHOD__);
return FALSE;
endif;
if(function_exists('finfo_open')) :
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$content_type = finfo_file($finfo, $resume_path);
finfo_close($finfo);
else :
$content_type = '';
endif;
$comm_args = array(
'externalID' => 'PORTFOLIO',
'fileType' => self::RESUME_FILE_TYPE,
'description' => 'Candidate resume',
'type' => 'resume',
'content_type' => $content_type,
'file' => array(
'filename' => empty($resume_filename) || !is_string($resume_filename) ? 'resume.txt' : trim($resume_filename),
'name' => 'Resume',
'path' => $resume_path,
),
);
if(FALSE === $this->comm(sprintf('file/Candidate/%d/raw', $candidate_id), 'FILE', $comm_args)) :
return FALSE;
endif;
if(!isset($this->comm_response['fileId'])) :
$this->errorsSet('fileId not present in response', __LINE__, __METHOD__);
return FALSE;
endif;
return TRUE;
}
public function candidateCreate($args=array(), $resume_path=NULL, $resume_filename=NULL) {
if(TRUE !== $this->oAuth()) :
return FALSE;
endif;
if(FALSE === ($comm_args = self::candidatePrepData($args))) :
return FALSE;
endif;
if(FALSE === $this->comm('entity/Candidate', 'PUT', $comm_args)) :
return FALSE;
endif;
if(NULL !== $resume_path) :
$candidate_id = $this->comm_response['changedEntityId'];
if(FALSE === $this->candidateAttachResume($candidate_id, $resume_path, $resume_filename)) :
if(TRUE === self::REWIND_CREATE_ON_RESUME_FAIL) :
$this->candidateDelete($candidate_id);
endif;
return FALSE;
endif;
endif;
return TRUE;
}
public function candidateDelete($candidate_ids=NULL) {
if(TRUE !== $this->oAuth()) :
return FALSE;
endif;
$candidate_ids = !is_array($candidate_ids) ? explode(',', $candidate_ids) : $candidate_ids;
$candidate_ids = array_map('trim', $candidate_ids);
$candidate_ids = array_filter($candidate_ids, 'strlen');
$candidate_ids = array_unique($candidate_ids);
foreach($candidate_ids as $candidate_id) :
if(!is_numeric($candidate_id)) :
ob_start(); var_dump($candidate_id);
$this->errorsSet(array(
'Delete aborted: Invalid candidate id',
'Candidate id: '.ob_get_clean(),
), __METHOD__, __LINE__);
continue;
endif;
$this->log('Deleting candidate '.$candidate_id);
$comm_args = array(
'isDeleted' => '1',
);
if(FALSE === $this->comm('entity/Candidate/'.$candidate_id, 'POST', $comm_args)) :
$this->log('Failed to delete candidate '.$candidate_id);
endif;
endforeach;
return 0 === sizeof($this->errors);
}
public function candidateFind($args=array(), $return_ids=FALSE) {
if(TRUE !== $this->oAuth()) :
return FALSE;
endif;
$args = array_merge(array(
'first_name' => '',
'last_name' => '',
'email' => '',
'is_deleted' => '0',
), $args);
$select_args = array(
'and' => $args,
);
if(NULL === ($query = $this->searchBuildQuery($select_args, array('id', 'first_name', 'last_name', 'email')))) :
$this->errorsSet(array(
'Failed to build search query',
), __LINE__, __METHOD__);
return FALSE;
endif;
if(FALSE === $this->comm(sprintf('search/Candidate?%s', $query), 'GET')) :
return FALSE;
endif;
if(!is_array($this->comm_response) || !isset($this->comm_response['total']) || 0 === $this->comm_response['total'] || !isset($this->comm_response['data'][0])) :
return NULL;
endif;
$perfect_matches = array();
foreach($this->comm_response['data'] as $match) :
if(1 !== (int) $match['_score']) :
continue;
endif;
$perfect_matches[] = $match;
endforeach;
if(empty($perfect_matches)) :
return NULL;
endif;
if(FALSE === $return_ids) :
return $perfect_matches;
endif;
$return = array();
foreach($perfect_matches as $match) :
$return[] = $match['id'];
endforeach;
return $return;
}
private static function candidatePrepData($data=array()) {
if(!is_array($data) || empty($data)) :
$this->errorsSet('Invalid candidate data', __LINE__, __METHOD__);
return FALSE;
endif;
if(isset($data['gender'])) :
$data['gender'] = substr($data['gender'], 0, 1);
endif;
if(isset($data['veteran'])) :
$data['veteran'] = substr($data['veteran'], 0, 1);
endif;
if(isset($data['occupation']) && 50 < strlen($data['occupation'])) :
$data['occupation'] = substr($data['occupation'], 0, 47).'...';
endif;
if(isset($data['referredBy']) && 50 < strlen($data['referredBy'])) :
$data['referredBy'] = substr($data['referredBy'], 0, 47).'...';
endif;
foreach(array('dateAvailable') as $date_field) :
if(isset($data[$date_field]) && !empty($data[$date_field])) :
if(FALSE === ($date_field_ts = strtotime($data[$date_field]))) :
unset($data[$date_field]);
else :
# Add 12 hours so BH admin doesn't use the incorrect day...
# 01/01/2018 was being inserted as 12/31/2017
$date_field_ts += 3600*12;
# Convert it to milliseconds so BH API knows what to do with it
# Convert it to an int so BH API doesn't complain about invalid scalar
$date_field_ts = (int) $date_field_ts*1000;
$data[$date_field] = $date_field_ts;
endif;
endif;
endforeach;
return $data;
}
public function candidateUpdate($candidate_id=NULL, $args=array(), $resume_path=NULL, $resume_filename=NULL) {
if(TRUE !== $this->oAuth()) :
return FALSE;
endif;
if(empty($candidate_id) || !is_numeric($candidate_id)) :
ob_start(); var_dump($candidate_id);
$this->errorsSet(array(
'Delete aborted: Invalid candidate id',
'Candidate id: '.ob_get_clean(),
), __METHOD__, __LINE__);
return FALSE;
endif;
if(FALSE === ($comm_args = self::candidatePrepData($args))) :
return FALSE;
endif;
if(FALSE === $this->comm('entity/Candidate/'.$candidate_id, 'POST', $comm_args)) :
return FALSE;
endif;
if(NULL !== $resume_path) :
if(FALSE === $this->candidateAttachResume($candidate_id, $resume_path, $resume_filename)) :
return FALSE;
endif;
endif;
return TRUE;
}
private function comm($endpoint_script=NULL, $endpoint_method=NULL, $endpoint_args=array(), $endpoint_url=NULL, $verify_response=TRUE) {
$endpoint_args = !is_array($endpoint_args) ? array() : $endpoint_args;
$this->comm_response = NULL;
$this->comm_response_info = NULL;
if(NULL === $endpoint_url) :
$endpoint_url = $this->rest_endpoint_url.ltrim($endpoint_script, '/');
endif;
if(!empty($this->rest_token)) :
$bh_token_param = '?BhRestToken='.urlencode($this->rest_token).'&';
if(strstr($endpoint_url, '?')) :
$endpoint_url = str_replace('?', $bh_token_param, $endpoint_url);
else :
$endpoint_url = $endpoint_url.$bh_token_param;
endif;
elseif(strstr($endpoint_url, '?')) :
$endpoint_url = rtrim($endpoint_url, '&').'&';
else :
$endpoint_url = $endpoint_url.'?';
endif;
switch($endpoint_method) :
case 'FILE' :
if(!isset($endpoint_args['file'])) :
$this->errorsSet('missing argument "file"', __LINE__, __METHOD__);
return FALSE;
endif;
$file = $endpoint_args['file'];
unset($endpoint_args['file']);
if(FALSE === is_readable($file['path'])) :
$this->errorsSet('Failed to locate path', __LINE__, __METHOD__);
return FALSE;
else :
$file['contents'] = file_get_contents($file['path']);
endif;
$multipart_name = !isset($file['name']) || empty($file['name']) ? 'Unknown' : $file['name'];
$multipart_filename = !isset($file['filename']) || empty($file['filename']) ? 'unknown' : $file['filename'];
$multipart_new_line = "\r\n";
$multipart_boundary = md5(time());
$multipart_body = '--'.$multipart_boundary.$multipart_new_line;
$multipart_body .= 'Content-Disposition: form-data; name="'.$multipart_name.'"; filename="'.$multipart_filename.'"'.$multipart_new_line;
$multipart_body .= 'Content-Length: '.strlen($file['contents']).$multipart_new_line;
$multipart_body .= 'Content-Type: application/octet-stream'.$multipart_new_line;
$multipart_body .= 'Content-Transfer-Encoding: binary'.$multipart_new_line.$multipart_new_line;
$multipart_body .= $file['contents'].$multipart_new_line;
$multipart_body .= '--'.$multipart_boundary.'--'.$multipart_new_line.$multipart_new_line;
$endpoint_url_qs = empty($endpoint_args) ? '' : http_build_query($endpoint_args);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint_url.$endpoint_url_qs);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::ENDPOINT_TIMEOUT);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); # Don't use CURLOPT_PUT (results in a "read timed out" error)
curl_setopt($ch, CURLOPT_BINARYTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $multipart_body);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: multipart/form-data; boundary='.$multipart_boundary,
));
break;
case 'GET' :
$endpoint_url_qs = empty($endpoint_args) ? '' : http_build_query($endpoint_args);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint_url.$endpoint_url_qs);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::ENDPOINT_TIMEOUT);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
break;
case 'POST' :
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint_url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::ENDPOINT_TIMEOUT);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
if(!empty($endpoint_args)) :
$endpoint_args_string = json_encode($endpoint_args);
curl_setopt($ch, CURLOPT_POSTFIELDS, $endpoint_args_string);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: '.strlen($endpoint_args_string),
));
endif;
break;
case 'PUT' :
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint_url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::ENDPOINT_TIMEOUT);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); # Don't use CURLOPT_PUT (results in a "read timed out")
if(!empty($endpoint_args)) :
$endpoint_args_string = json_encode($endpoint_args);
curl_setopt($ch, CURLOPT_POSTFIELDS, $endpoint_args_string);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: '.strlen($endpoint_args_string),
));
endif;
break;
default :
ob_start();
var_dump($method);
$this->errorsSet(array(
'Comm error (method)',
'Invalid endpoint method ('.ob_get_clean().')',
), __LINE__, __METHOD__);
return FALSE;
break;
endswitch;
$this->comm_response = curl_exec($ch);
$this->comm_response_info = curl_getinfo($ch);
curl_close($ch);
if(FALSE === $this->comm_response) :
$this->errorsSet(array(
'Comm error (cURL)',
'Endpoint: '.$endpoint_url,
'REST method: '.$endpoint_method,
'REST args: '.((empty($endpoint_args)) ? 'NULL' : json_encode($endpoint_args)),
'Error: '.curl_error($ch),
), __LINE__, __METHOD__);
return FALSE;
endif;
if(TRUE === $verify_response) :
if(NULL === ($this->comm_response = json_decode($this->comm_response, TRUE))) :
$this->errorsSet(array(
'Comm error (Failed to decode response)',
'Endpoint: '.$endpoint_url,
'REST method: '.$endpoint_method,
'REST args: '.((empty($endpoint_args)) ? 'NULL' : json_encode($endpoint_args)),
'Response: '.$this->comm_response,
'Response info: '.json_encode($this->comm_response_info),
), __LINE__, __METHOD__);
return FALSE;
endif;
if(!is_array($this->comm_response)) :
$this->errorsSet(array(
'Comm error (Response is not an array)',
'Endpoint: '.$endpoint_url,
'REST method: '.$endpoint_method,
'REST args: '.((empty($endpoint_args)) ? 'NULL' : json_encode($endpoint_args)),
'Response: '.$this->comm_response,
'Response info: '.json_encode($this->comm_response_info),
), __LINE__, __METHOD__);
return FALSE;
endif;
endif;
if(is_array($this->comm_response) && isset($this->comm_response['error']) || isset($this->comm_response['errorCode'])) :
$this->errorsSet(array(
'Comm error (Endpoint error)',
'Endpoint: '.$endpoint_url,
'REST method: '.$endpoint_method,
'REST args: '.((empty($endpoint_args)) ? 'NULL' : json_encode($endpoint_args)),
'Response: '.json_encode($this->comm_response),
'Response info: '.json_encode($this->comm_response_info),
), __LINE__, __METHOD__);
return FALSE;
endif;
return $this->comm_response;
}
public function errorsGetAll() {
return $this->errors;
}
public function errorsGetLast() {
return empty($this->errors) ? NULL : end($this->errors);
}
private function errorsSet($message=NULL, $line=NULL, $method=NULL) {
if(TRUE !== $this->is_dev) :
$message = self::PUBLIC_ERROR;
endif;
$message = !is_array($message) ? array($message) : $message;
$prefix = '';
$suffix = '';
if(TRUE !== $this->is_dev) :
$prefix = empty($line) ? '' : '[Err'.$line.']';
else :
array_unshift($message, 'Method: '.$method, 'Line: '.$line);
endif;
$message = implode('<br>', $message);
$message = trim($prefix.' '.$message.' '.$suffix);
$this->errors[] = array('method' => $method, 'line' => $line, 'message' => $message);
return TRUE;
}
private function log($msg) {
if(FALSE === $this->is_dev || FALSE === self::SHOW_LOG) :
return;
endif;
echo $msg.'<br>';
}
private function oAuth() {
if('' === self::CLIENT_ID || '' === self::CLIENT_SECRET || '' === self::API_USERNAME || '' === self::API_PASSWORD) :
$this->errorsSet(array(
'Empty client id, client secret, api username, or api password.',
'Follow instructions located at: http://developer.bullhorn.com/articles/getting_started',
'Hint: Request API access via support ticket',
), __LINE__, __METHOD__);
return FALSE;
endif;
if(TRUE !== $this->oAuthSetRestToken()) :
return FALSE;
endif;
if(TRUE !== $this->oAuthLogin()) :
return FALSE;
endif;
return TRUE;
}
private function oAuthLogin() {
if(FALSE === $this->comm(NULL, 'POST', $args, sprintf(self::ENDPOINT_REST_TOKEN, urlencode($this->access_token)))) :
return FALSE;
endif;
$this->rest_endpoint_url = $this->comm_response['restUrl'];
$this->rest_token = $this->comm_response['BhRestToken'];
return TRUE;
}
private function oAuthSetAuthCode($force=FALSE) {
if(FALSE === $force && !empty($this->auth_code)) :
return TRUE;
endif;
$comm_args = array(
'client_id' => self::CLIENT_ID,
'response_type' => 'code',
'username' => self::API_USERNAME,
'password' => self::API_PASSWORD,
'action' => 'Login',
);
$comm_url = sprintf(self::ENDPOINT_AUTH_CODE, http_build_query($comm_args));
if(FALSE === $this->comm(NULL, 'GET', NULL, $comm_url, FALSE)) :
return FALSE;
endif;
if(!empty($this->comm_response_info) && preg_match('@\?code=(.*)&@i', $this->comm_response_info['url'], $auth_code)) :
$this->auth_code = urldecode($auth_code[1]);
$this->log('Setting auth code: "'.$this->auth_code.'"');
return TRUE;
endif;
$this->errorsSet(array(
'Failed to retreive auth code',
'Endpoint: '.$comm_url,
'REST method: GET',
'REST args: '.((empty($comm_args)) ? 'NULL' : json_encode($comm_args)),
'Response: '.$this->comm_response,
'Response info: '.json_encode($this->comm_response_info),
), __LINE__, __METHOD__);
return FALSE;
}
private function oAuthSetRestToken() {
if(!empty($this->refresh_token)) :
$comm_args = array(
'grant_type' => 'refresh_token',
'refresh_token' => $this->refresh_token,
'client_id' => self::CLIENT_ID,
'client_secret' => self::CLIENT_SECRET,
);
if(FALSE === $this->comm(NULL, 'POST', NULL, sprintf(self::ENDPOINT_ACCESS_TOKEN, http_build_query($comm_args)))) :
return FALSE;
endif;
$this->log('Setting access token (via refresh): "'.$this->comm_response['access_token'].'"');
$this->log('Setting refresh token (via refresh): "'.$this->comm_response['refresh_token'].'"');
$this->access_token = $this->comm_response['access_token'];
$this->refresh_token = $this->comm_response['refresh_token'];
return TRUE;
endif;
if(FALSE === $this->oAuthSetAuthCode()) :
return FALSE;
endif;
# Fetch access token
$comm_args = array(
'grant_type' => 'authorization_code',
'code' => $this->auth_code,
'client_id' => self::CLIENT_ID,
'client_secret' => self::CLIENT_SECRET,
);
if(FALSE === $this->comm(NULL, 'POST', NULL, sprintf(self::ENDPOINT_ACCESS_TOKEN, http_build_query($comm_args)))) :
return FALSE;
endif;
$this->log('Setting access token: "'.$this->comm_response['access_token'].'"');
$this->log('Setting refresh token: "'.$this->comm_response['refresh_token'].'"');
$this->access_token = $this->comm_response['access_token'];
$this->refresh_token = $this->comm_response['refresh_token'];
return TRUE;
}
private function searchBuildQuery($query_fields=array(), $select_args=array()) {
$operator_groups = array('and', 'or', 'custom');
# internal_field => bullhorn_field
# This list could be greatly expanded...
$possible_fields = array(
'id' => 'id',
'ID' => 'id',
'first_name' => 'firstName',
'firstName' => 'firstName',
'last_name' => 'lastName',
'email' => 'email',
'is_deleted' => 'isDeleted',
'isDeleted' => 'isDeleted',
);
if(empty($query_fields)) :
$this->errorsSet('Query fields cannot be empty.', __LINE__, __METHOD__);
return FALSE;
endif;
$valid_operator = FALSE;
foreach($operator_groups as $operator) :
if(!isset($query_fields[$operator]) || empty($query_fields[$operator]) || !is_array($query_fields[$operator])) :
continue;
endif;
$valid_operator = TRUE;
break;
endforeach;
if(FALSE === $valid_operator) :
$this->errorsSet('Invalid query fields.', __LINE__, __METHOD__);
return FALSE;
endif;
# Translate $select_args into BH field names
foreach($select_args as $k => $v) :
if(!isset($possible_fields[$v])) :
unset($select_args[$operator][$k]);
continue;
endif;
$select_args[$k] = $possible_fields[$v];
endforeach;
if(empty($select_args)) :
$this->errorsSet('Select cannot be empty.', __LINE__, __METHOD__);
return FALSE;
endif;
$return = array(
'query' => '',
'fields' => implode(',', $select_args),
);
foreach($query_fields as $operator_group => $operator_group_fields) :
$return['query'] .= empty($return['query']) ? '' : ' AND ';
if('custom' === $operator_group) :
$return['query'] .= $operator_group_fields;
continue;
endif;
$operator_fields = array();
foreach($operator_group_fields as $operator_group_field => $operator_group_field_value) :
if(!isset($possible_fields[$operator_group_field]) || in_array(trim($operator_group_field_value), array(NULL, ''))) :
continue;
endif;
$operator_fields[] = $possible_fields[$operator_group_field].':'.$operator_group_field_value;
endforeach;
$return['query'] .= '('.implode(' '.strtoupper($operator_group).' ', $operator_fields).')';
endforeach;
if(empty($return['query'])) :
$this->errorsSet('Invalid query fields', __LINE__, __METHOD__);
return NULL;
endif;
return http_build_query($return);
}
}
// End BullhornAPI class
Code: Select all
<?php
require_once 'BullhornAPI.php';
# ...
if($_POST) :
$bh_obj = new BullhornAPI();
$candidate_data = $_POST;
$form_status = NULL;
# Validate resume upload
if(NULL === $form_status) :
if($_FILES) :
if(FALSE === ($resume = validateUpload('resume'))) :
$form_status = array('status' => 'error', 'message' => uploadError());
unset($resume);
endif;
endif;
endif;
# Look for duplicate applicant
if(NULL === $form_status) :
$duplicate_candidates = $bh_obj->candidateFind(array('first_name' => $candidate_data['firstName'], 'last_name' => $candidate_data['lastName'], 'email' => $candidate_data['email']), TRUE);
if(FALSE === $duplicate_candidates) :
if(NULL === ($bh_error = $bh_obj->errorsGetLast())) :
$bh_error = '('.__LINE__.') An unknown error occurred';
endif;
$form_status = array('status' => 'error', 'message' => $bh_error['message']);
elseif(NULL !== $duplicate_candidates) :
$duplicate_comment = 'DUP:'.implode(',', $duplicate_candidates);
# "occupation" is the field name, Quick Notes is the field label...
if(isset($candidate_data['occupation'])) :
$candidate_data['occupation'] = trim($candidate_data['occupation'])."\n\n".$duplicate_comment;
else :
$candidate_data['occupation'] = $duplicate_comment;
endif;
endif;
endif;
# Create the candidate
if(NULL === $form_status) :
$candidate_data = array_merge($candidate_data, array(
'firstName' => $candidate_data['firstName'],
'lastName' => $candidate_data['lastName'],
'name' => trim($candidate_data['firstName'].' '.$candidate_data['lastName']),
'description' => 'Website applicant',
'email' => $candidate_data['email'],
'address' => array(
'address1' => $candidate_data['address1'],
'address2' => $candidate_data['address2'],
'city' => $candidate_data['city'],
'state' => $candidate_data['state'],
'zip' => $candidate_data['zip'],
'countryID' => $candidate_data['countryID'],
),
));
# Ditch keys Bullhorn will complain about
unset($candidate_data['address1']);
unset($candidate_data['address2']);
unset($candidate_data['city']);
unset($candidate_data['state']);
unset($candidate_data['zip']);
unset($candidate_data['countryID']);
if($has_resume) :
$resume_filename = basename($resume['data']['tmp_name']);
$resume_path = $resume['data']['tmp_name'];
else :
$resume_filename = $resume_path = NULL;
endif;
if(FALSE === ($candidate_id = $bh_obj->candidateCreate($candidate_data, $resume_path, $resume_filename))) :
if(NULL === ($bh_error = $bh_obj->errorsGetLast())) :
$bh_error = '('.__LINE__.') An unknown error occurred';
endif;
$form_status = array('status' => 'error', 'message' => $bh_error['message']);
endif;
endif;
if(NULL === $form_status) :
redirect('to/success/page');
endif;
# Do something with $form_status
endif;
# ...