Help!: Error: Convert failed, resume mirror status: -1 - Rex has not been initialized 02244386

Forum for users and developers of Bullhorn's API service.

Moderators: StaffingSupport, s.emmons, BullhornSupport

Post Reply
annehiatt
User
Posts: 12
Joined: Tue Apr 19, 2016 7:42 pm

Help!: Error: Convert failed, resume mirror status: -1 - Rex has not been initialized 02244386

Post by annehiatt » Fri May 13, 2016 2:15 pm

We're experiencing an urgent problem where no resume is getting through to Bullhorn when the candidate is not linked to a specific job but just uploading their resume to the system. The error is:
Convert failed, resume mirror status: -1 - Rex has not been initialized

Can someone tell me what we need to add to this code to avoid this error? It looks like its being generated from the parseResume function?

Anne

Code: Select all

<?php


// require_once dirname( dirname( __FILE__ ) ) . '/bullhorn-2-wp.php';

/**
 * This class is an extension of Bullhorn_Connection.  Its purpose
 * is to allow for resume and candidate posting
 *
 * Class Bullhorn_Extended_Connection
 */
class Bullhorn_Extended_Connection extends Bullhorn_Connection {

	/**
	 * Class Constructor
	 *
	 * @return \Bullhorn_Extended_Connection
	 */
	public function __construct() {
		// Call parent __construct()
		parent::__construct();

		add_filter( 'query_vars', array( __CLASS__, 'add_query_vars' ) );
		add_action( 'init', array( __CLASS__, 'add_endpoint' ) );
		add_action( 'parse_request', array( __CLASS__, 'sniff_requests' ) );
	}

	/**
	 * Update vars
	 *
	 * @param $vars
	 *
	 * @return array
	 */
	public static function add_query_vars( $vars ) {
		$vars[] = '__api';
		$vars[] = 'endpoint';

		return $vars;
	}

	/**
	 * Initialize the reqrite rule
	 *
	 * @return void
	 */
	public static function add_endpoint() {
		add_rewrite_rule( '^api/bullhorn/([^/]+)/?', 'index.php?__api=1&endpoint=$matches[1]', 'top' );
	}

	/**
	 * Check to see if the request is a bullhorn API request
	 *
	 * @return void
	 */
	public static function sniff_requests() {
		global $wp;
		if ( isset( $wp->query_vars['__api'] ) && isset( $wp->query_vars['endpoint'] ) ) {
			switch ( $wp->query_vars['endpoint'] ) {
				case 'resume':
					if (
						! isset( $_POST['bullhorn_cv_form'] )
						|| ! wp_verify_nonce( $_POST['bullhorn_cv_form'], 'bullhorn_cv_form' )
					) {
						print __( 'Sorry, your nonce did not verify.', 'bh-staffing-job-listing-and-cv-upload-for-wp' );
						die();

					}
					//$bullhorn = new Bullhorn_Extended_Connection;

					// Get Resume
					$resume = self::parseResume();

					if ( false === $resume ) {
						// Redirect
						$settings  = (array) get_option( 'bullhorn_settings' );
						$permalink = add_query_arg( array(
							'bh_applied' => false,
						), get_permalink( $settings['thanks_page'] ) );

						header( "location: $permalink" );
						exit;
					}

					if ( is_array( $resume ) ) {
						$orig_url = $_POST['_wp_http_referer'];
						$url = add_query_arg(
							array(
								'bh-message' => rawurlencode( $resume['errorMessage'] ),

							), $orig_url
						);

						wp_safe_redirect( $url );
						die();
					}

					// Create candidate
					$candidate = self::createCandidate( $resume );

					// Attach education to candidate
					self::attachEducation( $resume, $candidate );

					// Attach work history to candidate
					self::attachWorkHistory( $resume, $candidate );
					//var_dump($resume->candidateWorkHistory);

					// Attach work history to candidate
					self::attachSkills( $resume, $candidate );

					// Attach resume file to candidate
					error_log( 'wp_upload_file_request: ' . self::wp_upload_file_request( $candidate ) );

				//	error_log( 'wp_upload_file_request: ' . self::wp_upload_html_request( $candidate ) );

					// link to job
					self::link_candidate_to_job( $candidate );

					do_action( 'wp-bullhorn-cv-upload-complete', $candidate, $resume );
					// Redirect
					$settings  = (array) get_option( 'bullhorn_settings' );
					$permalink = add_query_arg( array(
						'bh_applied' => true,
					), get_permalink( $settings['thanks_page'] ) );

					header( "location: $permalink" );
					exit;

					break;
				default:
					$response = array(
						'status' => 404,
						'error'  => __( 'The endpoint you are trying to reach does not exist.', 'bh-staffing-job-listing-and-cv-upload-for-wp' ),
					);
					echo json_encode( $response);
			}
			exit;
		}
	}

	/**
	 * Takes the posted 'resume' file and returns a parsed version from bullhorn
	 *
	 * @return mixed
	 */
	public static function parseResume() {

		// check to make sure file was posted
		if ( ! isset( $_FILES['resume'] ) ) {
			self::throwJsonError( 500, 'No "resume" file found.' );
		}
		list( $ext, $format ) = self::get_filetype();

		// API authentication
		self::apiAuth();

		// http://gerhardpotgieter.com/2014/07/30/uploading-files-with-wp_remote_post-or-wp_remote_request/
		$local_file = $_FILES['resume']['tmp_name'];
		// wp_remote_request way

		//https://github.com/jeckman/wpgplus/blob/master/gplus.php#L554
		$boundary = md5( time() );
		$payload  = '';
		$payload .= '--' . $boundary;
		$payload .= "\r\n";
		$payload .= 'Content-Disposition: form-data; name="photo_upload_file_name"; filename="' . $_FILES['resume']['name'] . '"' . "\r\n";
		$payload .= 'Content-Type: ' . $ext . '\r\n'; // If you	know the mime-type
		$payload .= 'Content-Transfer-Encoding: binary' . "\r\n";
		$payload .= "\r\n";
		$payload .= file_get_contents( $local_file );
		$payload .= "\r\n";
		$payload .= '--' . $boundary . '--';
		$payload .= "\r\n\r\n";

		$args = array(
			'method'  => 'POST',
			'headers' => array(
				'accept'       => 'application/json', // The API returns JSON
				'content-type' => 'multipart/form-data;boundary=' . $boundary, // Set content type to multipart/form-data
			),
			'body'    => $payload,
		);

		$url = add_query_arg(
			array(
				'BhRestToken' => self::$session,
				'format'      => $format,
				'populateDescription' => 'html',
			), self::$url . 'resume/parseToCandidate'
		);

		$response = wp_remote_request( $url, $args );


		if ( is_wp_error( $response ) ) {
			return false;
		}
		if ( 200 === $response['response']['code'] ) {
			return json_decode( $response['body'] );
		}

		return json_decode( $response['body'], true  );
	}

	/**
	 * Send a json error to the screen
	 *
	 * @param $status
	 * @param $error
	 */
	function throwJsonError( $status, $error ) {
		$response = array( 'status' => $status, 'error' => $error );
		echo json_encode( $response );
		exit;
	}

	/**
	 * @return array
	 */
	private static function get_filetype() {
		// Get file extension
		$file_type = wp_check_filetype_and_ext( $_FILES['resume']['tmp_name'], $_FILES['resume']['name'] );
		$ext       = $file_type['type'];

		switch ( strtolower( $ext ) ) {
			case 'text/plain':
				$format = 'TEXT';
				break;
			case 'application/msword':
				$format = 'DOC';
				break;
			case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
				$format = 'DOCX';
				break;
			case 'application/pdf':
				$format = 'PDF';
				break;
			case 'text/rtf':
				$format = 'RTF';
				break;
			case 'text/html':
				$format = 'HTML';
				break;
			default:
				$format = '';
				self::throwJsonError( 500, __( 'File format error. (txt, html, pdf, doc, docx, rft)', 'bh-staffing-job-listing-and-cv-upload-for-wp' ) );

				return array( $ext, $format );
		}

		return array( $ext, $format );
	}

	/**
	 * Run this before any api call.
	 *
	 * @return void
	 */
	private static function apiAuth() {
		// Refresh the token if necessary before doing anything
		if ( false === self::refresh_token() ) {
			return false;
		};

		// login to bullhorn api
		$logged_in = self::login();
		if ( ! $logged_in ) {
			self::throwJsonError( 500, __( 'There was a problem logging into the Bullhorn API.', 'bh-staffing-job-listing-and-cv-upload-for-wp' ) );
		}
	}

	/**
	 * Create a candidate int he system
	 *
	 * @param $resume
	 *
	 * @return mixed
	 */
	public static function createCandidate( $resume ) {

		if( ! isset( $resume->candidate ) ){
			return false;
		}

		// Make sure country ID is correct
		if ( isset( $resume->candidate->address ) && is_null( $resume->candidate->address->countryID ) ) {
			$resume->candidate->address->countryID = 1;
		}

		if ( isset( $_POST['email'] ) ) {
			$cv_email = $resume->candidate->email;

			$resume->candidate->email  = esc_attr( $_POST['email'] );
			$resume->candidate->email2 = esc_attr( $cv_email );
		}
		if ( isset( $_POST['phone'] ) ) {
			$cv_phone = $resume->candidate->phone;

			$resume->candidate->phone  = esc_attr( $_POST['phone'] );
			$resume->candidate->phone2 = esc_attr( $cv_phone );
		}
		if ( isset( $_POST['name'] ) ) {
			$resume->candidate->name = esc_attr( $_POST['name'] );
		}

		if ( isset( $_POST['address1'] ) ) {
			$cv_address     = $resume->candidate->address;
			$address_fields = array( 'address1', 'address2', 'city', 'state', 'zip' );
			$address_data   = array();

			foreach ( $address_fields as $key ) {
				$address_data[ $key ] = ( isset( $_POST[ $key ] ) ) ? $_POST[ $key ] : '';
			}
			$resume->candidate->address          = $address_data;
			$resume->candidate->secondaryAddress = $cv_address;

		}

		$resume->candidate->source = 'New Website';

		// API authentication
		self::apiAuth();

		$url = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . 'entity/Candidate'
		);

		$response = wp_remote_get( $url, array( 'body' => json_encode( $resume->candidate ), 'method' => 'PUT' ) );

		if ( ! is_wp_error( $response ) && 200 === $response['response']['code'] ) {

			return json_decode( $response['body'] );
		}

		return false;
	}

	/**
	 * Attach education to cantitates
	 *
	 * @param $resume
	 * @param $candidate
	 *
	 * @return mixed
	 */
	public static function attachEducation( $resume, $candidate ) {

		if ( empty( $resume->candidateEducation ) ) {
			return false;
		}

		// API authentication
		self::apiAuth();

		$responses = array();
		$url       = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . 'entity/CandidateEducation'
		);

		foreach ( $resume->candidateEducation as $edu ) {
			$edu->candidate     = new stdClass;
			$edu->candidate->id = $candidate->changedEntityId;
			if ( ! is_int( $edu->gpa ) || ! is_float( $edu->gpa ) ) {
				unset( $edu->gpa );
			}

			//$edu_data = json_encode( $edu );

			$response = wp_remote_get( $url, array( 'body' => json_encode( $edu ), 'method' => 'PUT' ) );

			if ( 200 === $response['response']['code'] ) {
				$responses[] = wp_remote_retrieve_body( $response );
			}
		}

		return json_decode( '[' . implode( ',', $responses ) . ']' );
	}

	/**
	 * Attach Work History to a candidate
	 *
	 * @param $resume
	 * @param $candidate
	 *
	 * @return mixed
	 */
	public static function attachWorkHistory( $resume, $candidate ) {

		if ( empty( $resume->candidateWorkHistory ) ) {
			return false;
		}
		// API authentication
		self::apiAuth();

		// Create the url && variables array
		$responses = array();
		$url       = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . 'entity/CandidateWorkHistory'
		);

		foreach ( $resume->candidateWorkHistory as $job ) {

			$job->candidate     = new stdClass;
			$job->candidate->id = $candidate->changedEntityId;

			$response = wp_remote_get( $url, array( 'body' => json_encode( $job ), 'method' => 'PUT' ) );

			if ( 200 === $response['response']['code'] ) {
				$responses[] = wp_remote_retrieve_body( $response );
			}
		}

		return json_decode( '[' . implode( ',', $responses ) . ']' );
	}

	/**
	 * Attach Work History to a candidate
	 *
	 * @param $resume
	 * @param $candidate
	 *
	 * @return mixed
	 */
	public static function attachSkills( $resume, $candidate ) {

		if ( empty( $resume->skillList ) ) {
			return false;
		}
		// API authentication
		self::apiAuth();

		$skillList = self::get_skill_list();

		$skill_ids = array();
		if ( ! empty( $skill_ids ) ) {
			foreach ( $resume->skillList as $skill ) {
				if ( false !== $key = array_search( strtolower( $skill ), $skillList ) ) {
					$skill_ids[] = $key;
				}
			}
			$skill_ids = array_unique( $skill_ids );
		}


		// Create the url && variables array
		$url      = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . '/entity/Candidate/' . $candidate->changedEntityId . '/primarySkills/' . implode( ',', $skill_ids )
		);
		$response = wp_remote_get( $url, array( 'method' => 'PUT' ) );

		if ( 200 === $response['response']['code'] ) {
			return wp_remote_retrieve_body( $response );
		}

		return false;
	}

	/**
	 * @return array $skill_list
	 */
	public static function get_skill_list() {
		$skill_list_id = 'bullhorn_skill_list';

		$skill_list = get_transient( $skill_list_id );
		if ( false === $skill_list ) {
			$skill_list = array();
			$url        = add_query_arg(
				array(
					'BhRestToken' => self::$session,
				), self::$url . 'options/Skill'
			);

			$response = wp_remote_get( $url, array( 'method' => 'GET' ) );
			$body     = wp_remote_retrieve_body( $response );

			$data = json_decode( $body, true );
			if ( isset( $data['data'] ) ) {
				return $skill_list;
			}
			$data = $data['data'];

			$skill_list = array();
			foreach ( $data as $skill ) {
				$skill_list[ $skill['value'] ] = self::clean_skill_label( $skill['label'] );
			}

			set_transient( $skill_list_id, $skill_list, HOUR_IN_SECONDS * 6 );
		}

		return $skill_list;
	}

	private static function clean_skill_label( $label ) {
		$label = strtolower( trim( $label ) );

		return $label;
	}
	/**
	 * @param $candidate
	 *
	 * @return array|bool|mixed|object
	 */
	public static function wp_upload_file_request( $candidate ) {

		list( $ext, $format ) = self::get_filetype();

		$local_file = $_FILES['resume']['tmp_name'];
		// wp_remote_request way

		//https://github.com/jeckman/wpgplus/blob/master/gplus.php#L554
		$boundary = md5( time() . $ext );
		$payload  = '';
		$payload .= '--' . $boundary;
		$payload .= "\r\n";
		$payload .= 'Content-Disposition: form-data; name="photo_upload_file_name"; filename="' . $_FILES['resume']['name'] . '"' . "\r\n";
		$payload .= 'Content-Type: ' . $format . '\r\n'; // If you	know the mime-type
		$payload .= 'Content-Transfer-Encoding: binary' . "\r\n";
		$payload .= "\r\n";
		$payload .= file_get_contents( $local_file );
		$payload .= "\r\n";
		$payload .= '--' . $boundary . '--';
		$payload .= "\r\n\r\n";

		$args = array(
			'method'  => 'PUT',
			'headers' => array(
				'accept'       => 'application/json', // The API returns JSON
				'content-type' => 'multipart/form-data;boundary=' . $boundary, // Set content type to multipart/form-data
			),
			'body'    => $payload,
		);

		$url      = add_query_arg(
			array(
				'BhRestToken' => self::$session,
				'externalID'  => 'Portfolio',
				'fileType'    => 'SAMPLE',
			), self::$url . '/file/Candidate/' . $candidate->changedEntityId . '/raw'
		);
		$response = wp_remote_request( $url, $args );

		// try once more if we get an error
		if ( is_wp_error( $response ) || 201 !== $response['response']['code'] ) {
			$response = wp_remote_request( $url, $args );
		}

		if ( 200 === $response['response']['code'] ) {
			return json_decode( $response['body'] );
		}

		return false;

	}

	/**
	 * @param $candidate
	 *
	 * @return array|bool|mixed|object
	 */
	public static function wp_upload_html_request( $candidate ) {

		list( $ext, $format ) = self::get_filetype();

		$local_file = $_FILES['resume']['tmp_name'];
		// wp_remote_request way

		//https://github.com/jeckman/wpgplus/blob/master/gplus.php#L554
		$boundary = md5( time() . $ext );
		$payload  = '';
		$payload .= '--' . $boundary;
		$payload .= "\r\n";
		$payload .= 'Content-Disposition: form-data; name="photo_upload_file_name"; filename="' . $_FILES['resume']['name'] . '"' . "\r\n";
		$payload .= 'Content-Type: ' . $format . '\r\n'; // If you	know the mime-type
		$payload .= 'Content-Transfer-Encoding: binary' . "\r\n";
		$payload .= "\r\n";
		$payload .= file_get_contents( $local_file );
		$payload .= "\r\n";
		$payload .= '--' . $boundary . '--';
		$payload .= "\r\n\r\n";

		$args = array(
			'method'  => 'PUT',
			'headers' => array(
				'accept'       => 'application/json', // The API returns JSON
				'content-type' => 'multipart/form-data;boundary=' . $boundary, // Set content type to multipart/form-data
			),
			'body'    => $payload,
		);

		$url      = add_query_arg(
			array(
				'BhRestToken' => self::$session,
				'format'  => $ext,
			), self::$url . '}/resume/convertToHTML'
		);
		$response = wp_remote_request( $url, $args );

		// try once more if we get an error
		if ( is_wp_error( $response ) || 201 !== $response['response']['code'] ) {
			$response = wp_remote_request( $url, $args );
		}


		//https://github.com/jeckman/wpgplus/blob/master/gplus.php#L554
		$boundary = md5( time() . $ext );
		$payload  = '';
		$payload .= '--' . $boundary;
		$payload .= "\r\n";
		$payload .= 'Content-Disposition: form-data; name="photo_upload_file_name"; filename="' . $_FILES['resume']['name'] . '"' . "\r\n";
		$payload .= 'Content-Type: ' . $format . '\r\n'; // If you	know the mime-type
		$payload .= 'Content-Transfer-Encoding: binary' . "\r\n";
		$payload .= "\r\n";
		$payload .= $response;
		$payload .= "\r\n";
		$payload .= '--' . $boundary . '--';
		$payload .= "\r\n\r\n";

		$args = array(
			'method'  => 'PUT',
			'headers' => array(
				'accept'       => 'application/json', // The API returns JSON
				'content-type' => 'multipart/form-data;boundary=' . $boundary, // Set content type to multipart/form-data
			),
			'body'    => $payload,
		);

		$url      = add_query_arg(
			array(
				'BhRestToken' => self::$session,
				'externalID'  => 'Portfolio',
				'fileType'    => 'SAMPLE',
			), self::$url . '/file/Candidate/' . $candidate->changedEntityId . '/raw'
		);
		$response = wp_remote_request( $url, $args );

		// try once more if we get an error
		if ( is_wp_error( $response ) || 201 !== $response['response']['code'] ) {
			$response = wp_remote_request( $url, $args );
		}

		if ( 200 === $response['response']['code'] ) {
			return json_decode( $response['body'] );
		}

		return false;

	}

	/**
	 * Link a candidate to job.
	 *
	 * @param $candidate
	 *
	 * @return mixed
	 */
	public static function link_candidate_to_job( $candidate ) {
		// API authentication
		self::apiAuth();

		if ( ! isset( $_POST['position'] ) ) {
			return false;
		}
		$jobOrder = absint( $_POST['position'] );

		$url = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . 'entity/JobSubmission'
		);

		$body = array(
			'candidate'       => array( 'id' => absint( $candidate->changedEntityId ) ),
			'jobOrder'        => array( 'id' => absint( $jobOrder ) ),
			'status'          => 'New Lead',
			'dateWebResponse' => self::microtime_float(), //date( 'u', $date ),// time(),
		);

		$response = wp_remote_get( $url, array( 'body' => json_encode( $body ), 'method' => 'PUT' ) );

		if ( 200 === $response['response']['code'] ) {
			return json_decode( $response['body'] );
		}

		return false;
	}

	/**
	 * get time in microseconds
	 * @return float
	 */
	private function microtime_float() {
		list( $usec, $sec ) = explode( ' ', microtime() );

		return ( (float) $usec + (float) $sec ) * 100;
	}
}

new Bullhorn_Extended_Connection();

SO-BHSupport
User
Posts: 24
Joined: Thu Feb 27, 2014 12:49 pm

Re: Help!: Error: Convert failed, resume mirror status: -1 - Rex has not been initialized

Post by SO-BHSupport » Mon May 16, 2016 10:01 am

Hi Anne,

This is Simon from Bullhorn Support and I will be assisting you with this query.

The "Rex has not been initalized" response is an intermittent problem that the parseResume function can generate, and is related to communication problems between Bullhorns services and our parsing providers system.

What we recommend developers build with their integrations is a retry logic where at the end of the call you monitor the response, and if it has provided an error message matching "Rex has not been initalized" you then try the call again until it is successful or hits 10 retries (or a similar number, since at that point it is almost guaranteed a problem with the resume as opposed to our service).

I do understand the delays this can cause and if you would like to discuss how to give this a more seamless front end experience I am more then happy to brainstorm ideas.

Kind regards,

Simon O'Keeffe
Simon O'Keeffe
Enterprise Support Team Lead
B U L L H O R N
Staffing and Recruiting Software, On Target, On Demand
617-478-9126 (US Support)
+44 800 032 2848 option 1 (UK Support)

annehiatt
User
Posts: 12
Joined: Tue Apr 19, 2016 7:42 pm

Re: Help!: Error: Convert failed, resume mirror status: -1 - Rex has not been initialized 02244386

Post by annehiatt » Mon May 23, 2016 2:57 pm

Thanks for your help Simon. We have updated our code, and we are no longer seeing the rex mirror error. However, we are having issues with Candidates linking to Jobs correctly and showing up in Submissions in Bullhorn. They are showing up in Candidates but not as attached to jobs. Can you let me know if you see anything here that might be causing this error?

Code: Select all

<?php


// require_once dirname( dirname( __FILE__ ) ) . '/bullhorn-2-wp.php';

/**
 * This class is an extension of Bullhorn_Connection.  Its purpose
 * is to allow for resume and candidate posting
 *
 * Class Bullhorn_Extended_Connection
 */
class Bullhorn_Extended_Connection extends Bullhorn_Connection {

	/**
	 * Class Constructor
	 *
	 * @return \Bullhorn_Extended_Connection
	 */
	public function __construct() {
		// Call parent __construct()
		parent::__construct();

		add_filter( 'query_vars', array( __CLASS__, 'add_query_vars' ) );
		add_action( 'init', array( __CLASS__, 'add_endpoint' ) );
		add_action( 'parse_request', array( __CLASS__, 'sniff_requests' ) );
	}

	/**
	 * Update vars
	 *
	 * @param $vars
	 *
	 * @return array
	 */
	public static function add_query_vars( $vars ) {
		$vars[] = '__api';
		$vars[] = 'endpoint';

		return $vars;
	}

	/**
	 * Initialize the reqrite rule
	 *
	 * @return void
	 */
	public static function add_endpoint() {
		add_rewrite_rule( '^api/bullhorn/([^/]+)/?', 'index.php?__api=1&endpoint=$matches[1]', 'top' );
	}

	/**
	 * Check to see if the request is a bullhorn API request
	 *
	 * @return void
	 */
	public static function sniff_requests() {
		global $wp;
		if ( isset( $wp->query_vars['__api'] ) && isset( $wp->query_vars['endpoint'] ) ) {
			switch ( $wp->query_vars['endpoint'] ) {
				case 'resume':
					if (
						! isset( $_POST['bullhorn_cv_form'] )
						|| ! wp_verify_nonce( $_POST['bullhorn_cv_form'], 'bullhorn_cv_form' )
					) {
						print __( 'Sorry, your nonce did not verify.', 'bh-staffing-job-listing-and-cv-upload-for-wp' );
						die();

					}
					//$bullhorn = new Bullhorn_Extended_Connection;

					// Get Resume
					$resume = self::parseResume();

					if ( false === $resume ) {
						// Redirect
						$settings  = (array) get_option( 'bullhorn_settings' );
						$permalink = add_query_arg( array(
							'bh_applied' => false,
						), get_permalink( $settings['thanks_page'] ) );

						header( "location: $permalink" );
						exit;
					}

					if ( is_array( $resume ) ) {
						$orig_url = $_POST['_wp_http_referer'];
						$url = add_query_arg(
							array(
								'bh-message' => rawurlencode( apply_filters( 'parse_resume_failed_text', $resume['errorMessage'] ) ),

							), $orig_url
						);

						wp_safe_redirect( $url );
						die();
					}

					// Create candidate
					$candidate = self::createCandidate( $resume );

					// Attach education to candidate
					self::attachEducation( $resume, $candidate );

					// Attach work history to candidate
					self::attachWorkHistory( $resume, $candidate );
					//var_dump($resume->candidateWorkHistory);

					// Attach work history to candidate
					self::attachSkills( $resume, $candidate );

					// Attach resume file to candidate
					error_log( 'wp_upload_file_request: ' . self::wp_upload_file_request( $candidate ) );

				//	error_log( 'wp_upload_file_request: ' . self::wp_upload_html_request( $candidate ) );

					// link to job
					self::link_candidate_to_job( $candidate );

					do_action( 'wp-bullhorn-cv-upload-complete', $candidate, $resume );
					// Redirect
					$settings  = (array) get_option( 'bullhorn_settings' );
					$permalink = add_query_arg( array(
						'bh_applied' => true,
					), get_permalink( $settings['thanks_page'] ) );

					header( "location: $permalink" );
					exit;

					break;
				default:
					$response = array(
						'status' => 404,
						'error'  => __( 'The endpoint you are trying to reach does not exist.', 'bh-staffing-job-listing-and-cv-upload-for-wp' ),
					);
					echo json_encode( $response);
			}
			exit;
		}
	}

	/**
	 * Takes the posted 'resume' file and returns a parsed version from bullhorn
	 *
	 * @return mixed
	 */
	public static function parseResume() {

		// check to make sure file was posted
		if ( ! isset( $_FILES['resume'] ) ) {
			self::throwJsonError( 500, 'No "resume" file found.' );
		}
		list( $ext, $format ) = self::get_filetype();

		// API authentication
		self::apiAuth();

		// http://gerhardpotgieter.com/2014/07/30/uploading-files-with-wp_remote_post-or-wp_remote_request/
		$local_file = $_FILES['resume']['tmp_name'];
		// wp_remote_request way

		//https://github.com/jeckman/wpgplus/blob/master/gplus.php#L554
		$boundary = md5( time() );
		$payload  = '';
		$payload .= '--' . $boundary;
		$payload .= "\r\n";
		$payload .= 'Content-Disposition: form-data; name="photo_upload_file_name"; filename="' . $_FILES['resume']['name'] . '"' . "\r\n";
		$payload .= 'Content-Type: ' . $ext . '\r\n'; // If you	know the mime-type
		$payload .= 'Content-Transfer-Encoding: binary' . "\r\n";
		$payload .= "\r\n";
		$payload .= file_get_contents( $local_file );
		$payload .= "\r\n";
		$payload .= '--' . $boundary . '--';
		$payload .= "\r\n\r\n";

		$args = array(
			'method'  => 'POST',
			'headers' => array(
				'accept'       => 'application/json', // The API returns JSON
				'content-type' => 'multipart/form-data;boundary=' . $boundary, // Set content type to multipart/form-data
			),
			'body'    => $payload,
		);

		$url = add_query_arg(
			array(
				'BhRestToken' => self::$session,
				'format'      => $format,
				'populateDescription' => 'html',
			), self::$url . 'resume/parseToCandidate'
		);

		$safety_count = 0;
		// make call to the parse the CV
		$response = wp_remote_request( $url, $args );
		while ( 10 > $safety_count ) {

			// sometime we will get an REX error this is due to a comms failer between bullhorn servers aand the 3rd party servers

			// if are good exit while loop
			if ( ! is_wp_error( $response ) && isset( $response['body'] ) && false === strpos( strtolower( $response['body'] ), 'convert failed' ) ) {
				break;
			}
			if ( is_wp_error( $response ) ) {
				error_log( 'CV parse looped with : ' . serialize( $response ) . ': ' . $safety_count );
			} elseif ( isset( $response['errorMessage'] ) ) {
				error_log( 'CV parse looped with : ' . $response['errorMessage'] . ': ' . $safety_count );
			}

			// make a attempt call to the parse the CV
			$response = wp_remote_request( $url, $args );
			$safety_count ++;
		}


		if ( is_wp_error( $response ) ) {
			return false;
		}

		if ( 200 === $response['response']['code'] ) {
			return json_decode( $response['body'] );
		}

		return json_decode( $response['body'], true );
	}

	/**
	 * Send a json error to the screen
	 *
	 * @param $status
	 * @param $error
	 */
	function throwJsonError( $status, $error ) {
		$response = array( 'status' => $status, 'error' => $error );
		echo json_encode( $response );
		exit;
	}

	/**
	 * @return array
	 */
	private static function get_filetype() {
		// Get file extension
		$file_type = wp_check_filetype_and_ext( $_FILES['resume']['tmp_name'], $_FILES['resume']['name'] );
		$ext       = $file_type['type'];

		switch ( strtolower( $ext ) ) {
			case 'text/plain':
				$format = 'TEXT';
				break;
			case 'application/msword':
				$format = 'DOC';
				break;
			case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
				$format = 'DOCX';
				break;
			case 'application/pdf':
				$format = 'PDF';
				break;
			case 'text/rtf':
				$format = 'RTF';
				break;
			case 'text/html':
				$format = 'HTML';
				break;
			default:
				$format = '';
				self::throwJsonError( 500, __( "Oops. This document isn't the correct format. Please upload it as one of the following formats: .txt, .html, .pdf, .doc, .docx, .rft.", 'bh-staffing-job-listing-and-cv-upload-for-wp' ) );

				return array( $ext, $format );
		}

		return array( $ext, $format );
	}

	/**
	 * Run this before any api call.
	 *
	 * @return void
	 */
	private static function apiAuth() {
		// Refresh the token if necessary before doing anything
		if ( false === self::refresh_token() ) {
			return false;
		};

		// login to bullhorn api
		$logged_in = self::login();
		if ( ! $logged_in ) {
			self::throwJsonError( 500, __( 'There was a problem logging into the Bullhorn API.', 'bh-staffing-job-listing-and-cv-upload-for-wp' ) );
		}
	}

	/**
	 * Create a candidate int he system
	 *
	 * @param $resume
	 *
	 * @return mixed
	 */
	public static function createCandidate( $resume ) {

		if( ! isset( $resume->candidate ) ){
			return false;
		}

		// Make sure country ID is correct
		if ( isset( $resume->candidate->address ) && is_null( $resume->candidate->address->countryID ) ) {
			$resume->candidate->address->countryID = 1;
		}

		if ( isset( $_POST['email'] ) ) {
			$cv_email = $resume->candidate->email;

			$resume->candidate->email  = esc_attr( $_POST['email'] );
			$resume->candidate->email2 = esc_attr( $cv_email );
		}
		if ( isset( $_POST['phone'] ) ) {
			$cv_phone = $resume->candidate->phone;

			$resume->candidate->phone  = esc_attr( $_POST['phone'] );
			$resume->candidate->phone2 = esc_attr( $cv_phone );
		}
		if ( isset( $_POST['name'] ) ) {
			$resume->candidate->name = esc_attr( $_POST['name'] );
		}

		if ( isset( $_POST['address1'] ) ) {
			$cv_address     = $resume->candidate->address;
			$address_fields = array( 'address1', 'address2', 'city', 'state', 'zip' );
			$address_data   = array();

			foreach ( $address_fields as $key ) {
				$address_data[ $key ] = ( isset( $_POST[ $key ] ) ) ? $_POST[ $key ] : '';
			}
			$resume->candidate->address          = $address_data;
			$resume->candidate->secondaryAddress = $cv_address;

		}

		$resume->candidate->source = 'New Website';

		// API authentication
		self::apiAuth();

		$url = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . 'entity/Candidate'
		);

		$response = wp_remote_get( $url, array( 'body' => json_encode( $resume->candidate ), 'method' => 'PUT' ) );

		if ( ! is_wp_error( $response ) && 200 === $response['response']['code'] ) {

			return json_decode( $response['body'] );
		}

		return false;
	}

	/**
	 * Attach education to cantitates
	 *
	 * @param $resume
	 * @param $candidate
	 *
	 * @return mixed
	 */
	public static function attachEducation( $resume, $candidate ) {

		if ( empty( $resume->candidateEducation ) ) {
			return false;
		}

		// API authentication
		self::apiAuth();

		$responses = array();
		$url       = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . 'entity/CandidateEducation'
		);

		foreach ( $resume->candidateEducation as $edu ) {
			$edu->candidate     = new stdClass;
			$edu->candidate->id = $candidate->changedEntityId;
			if ( ! is_int( $edu->gpa ) || ! is_float( $edu->gpa ) ) {
				unset( $edu->gpa );
			}

			//$edu_data = json_encode( $edu );

			$response = wp_remote_get( $url, array( 'body' => json_encode( $edu ), 'method' => 'PUT' ) );

			if ( 200 === $response['response']['code'] ) {
				$responses[] = wp_remote_retrieve_body( $response );
			}
		}

		return json_decode( '[' . implode( ',', $responses ) . ']' );
	}

	/**
	 * Attach Work History to a candidate
	 *
	 * @param $resume
	 * @param $candidate
	 *
	 * @return mixed
	 */
	public static function attachWorkHistory( $resume, $candidate ) {

		if ( empty( $resume->candidateWorkHistory ) ) {
			return false;
		}
		// API authentication
		self::apiAuth();

		// Create the url && variables array
		$responses = array();
		$url       = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . 'entity/CandidateWorkHistory'
		);

		foreach ( $resume->candidateWorkHistory as $job ) {

			$job->candidate     = new stdClass;
			$job->candidate->id = $candidate->changedEntityId;

			$response = wp_remote_get( $url, array( 'body' => json_encode( $job ), 'method' => 'PUT' ) );

			if ( 200 === $response['response']['code'] ) {
				$responses[] = wp_remote_retrieve_body( $response );
			}
		}

		return json_decode( '[' . implode( ',', $responses ) . ']' );
	}

	/**
	 * Attach Work History to a candidate
	 *
	 * @param $resume
	 * @param $candidate
	 *
	 * @return mixed
	 */
	public static function attachSkills( $resume, $candidate ) {

		if ( empty( $resume->skillList ) ) {
			return false;
		}
		// API authentication
		self::apiAuth();

		$skillList = self::get_skill_list();

		$skill_ids = array();
		if ( ! empty( $skill_ids ) ) {
			foreach ( $resume->skillList as $skill ) {
				if ( false !== $key = array_search( strtolower( $skill ), $skillList ) ) {
					$skill_ids[] = $key;
				}
			}
			$skill_ids = array_unique( $skill_ids );
		}


		// Create the url && variables array
		$url      = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . '/entity/Candidate/' . $candidate->changedEntityId . '/primarySkills/' . implode( ',', $skill_ids )
		);
		$response = wp_remote_get( $url, array( 'method' => 'PUT' ) );

		if ( 200 === $response['response']['code'] ) {
			return wp_remote_retrieve_body( $response );
		}

		return false;
	}

	/**
	 * @return array $skill_list
	 */
	public static function get_skill_list() {
		$skill_list_id = 'bullhorn_skill_list';

		$skill_list = get_transient( $skill_list_id );
		if ( false === $skill_list ) {
			$skill_list = array();
			$url        = add_query_arg(
				array(
					'BhRestToken' => self::$session,
				), self::$url . 'options/Skill'
			);

			$response = wp_remote_get( $url, array( 'method' => 'GET' ) );
			$body     = wp_remote_retrieve_body( $response );

			$data = json_decode( $body, true );
			if ( isset( $data['data'] ) ) {
				return $skill_list;
			}
			$data = $data['data'];

			$skill_list = array();
			foreach ( $data as $skill ) {
				$skill_list[ $skill['value'] ] = self::clean_skill_label( $skill['label'] );
			}

			set_transient( $skill_list_id, $skill_list, HOUR_IN_SECONDS * 6 );
		}

		return $skill_list;
	}

	private static function clean_skill_label( $label ) {
		$label = strtolower( trim( $label ) );

		return $label;
	}
	/**
	 * @param $candidate
	 *
	 * @return array|bool|mixed|object
	 */
	public static function wp_upload_file_request( $candidate ) {

		list( $ext, $format ) = self::get_filetype();

		$local_file = $_FILES['resume']['tmp_name'];
		// wp_remote_request way

		//https://github.com/jeckman/wpgplus/blob/master/gplus.php#L554
		$boundary = md5( time() . $ext );
		$payload  = '';
		$payload .= '--' . $boundary;
		$payload .= "\r\n";
		$payload .= 'Content-Disposition: form-data; name="photo_upload_file_name"; filename="' . $_FILES['resume']['name'] . '"' . "\r\n";
		$payload .= 'Content-Type: ' . $format . '\r\n'; // If you	know the mime-type
		$payload .= 'Content-Transfer-Encoding: binary' . "\r\n";
		$payload .= "\r\n";
		$payload .= file_get_contents( $local_file );
		$payload .= "\r\n";
		$payload .= '--' . $boundary . '--';
		$payload .= "\r\n\r\n";

		$args = array(
			'method'  => 'PUT',
			'headers' => array(
				'accept'       => 'application/json', // The API returns JSON
				'content-type' => 'multipart/form-data;boundary=' . $boundary, // Set content type to multipart/form-data
			),
			'body'    => $payload,
		);

		$url      = add_query_arg(
			array(
				'BhRestToken' => self::$session,
				'externalID'  => 'Portfolio',
				'fileType'    => 'SAMPLE',
			), self::$url . '/file/Candidate/' . $candidate->changedEntityId . '/raw'
		);
		$response = wp_remote_request( $url, $args );

		// try once more if we get an error
		if ( is_wp_error( $response ) || 201 !== $response['response']['code'] ) {
			$response = wp_remote_request( $url, $args );
		}

		if ( 200 === $response['response']['code'] ) {
			return json_decode( $response['body'] );
		}

		return false;

	}

	/**
	 * @param $candidate
	 *
	 * @return array|bool|mixed|object
	 */
	public static function wp_upload_html_request( $candidate ) {

		list( $ext, $format ) = self::get_filetype();

		$local_file = $_FILES['resume']['tmp_name'];
		// wp_remote_request way

		//https://github.com/jeckman/wpgplus/blob/master/gplus.php#L554
		$boundary = md5( time() . $ext );
		$payload  = '';
		$payload .= '--' . $boundary;
		$payload .= "\r\n";
		$payload .= 'Content-Disposition: form-data; name="photo_upload_file_name"; filename="' . $_FILES['resume']['name'] . '"' . "\r\n";
		$payload .= 'Content-Type: ' . $format . '\r\n'; // If you	know the mime-type
		$payload .= 'Content-Transfer-Encoding: binary' . "\r\n";
		$payload .= "\r\n";
		$payload .= file_get_contents( $local_file );
		$payload .= "\r\n";
		$payload .= '--' . $boundary . '--';
		$payload .= "\r\n\r\n";

		$args = array(
			'method'  => 'PUT',
			'headers' => array(
				'accept'       => 'application/json', // The API returns JSON
				'content-type' => 'multipart/form-data;boundary=' . $boundary, // Set content type to multipart/form-data
			),
			'body'    => $payload,
		);

		$url      = add_query_arg(
			array(
				'BhRestToken' => self::$session,
				'format'  => $ext,
			), self::$url . '}/resume/convertToHTML'
		);
		$response = wp_remote_request( $url, $args );

		// try once more if we get an error
		if ( is_wp_error( $response ) || 201 !== $response['response']['code'] ) {
			$response = wp_remote_request( $url, $args );
		}


		//https://github.com/jeckman/wpgplus/blob/master/gplus.php#L554
		$boundary = md5( time() . $ext );
		$payload  = '';
		$payload .= '--' . $boundary;
		$payload .= "\r\n";
		$payload .= 'Content-Disposition: form-data; name="photo_upload_file_name"; filename="' . $_FILES['resume']['name'] . '"' . "\r\n";
		$payload .= 'Content-Type: ' . $format . '\r\n'; // If you	know the mime-type
		$payload .= 'Content-Transfer-Encoding: binary' . "\r\n";
		$payload .= "\r\n";
		$payload .= $response;
		$payload .= "\r\n";
		$payload .= '--' . $boundary . '--';
		$payload .= "\r\n\r\n";

		$args = array(
			'method'  => 'PUT',
			'headers' => array(
				'accept'       => 'application/json', // The API returns JSON
				'content-type' => 'multipart/form-data;boundary=' . $boundary, // Set content type to multipart/form-data
			),
			'body'    => $payload,
		);

		$url      = add_query_arg(
			array(
				'BhRestToken' => self::$session,
				'externalID'  => 'Portfolio',
				'fileType'    => 'SAMPLE',
			), self::$url . '/file/Candidate/' . $candidate->changedEntityId . '/raw'
		);
		$response = wp_remote_request( $url, $args );

		// try once more if we get an error
		if ( is_wp_error( $response ) || 201 !== $response['response']['code'] ) {
			$response = wp_remote_request( $url, $args );
		}

		if ( 200 === $response['response']['code'] ) {
			return json_decode( $response['body'] );
		}

		return false;

	}

	/**
	 * Link a candidate to job.
	 *
	 * @param $candidate
	 *
	 * @return mixed
	 */
	public static function link_candidate_to_job( $candidate ) {
		// API authentication
		self::apiAuth();

		if ( ! isset( $_POST['position'] ) ) {
			return false;
		}
		$jobOrder = absint( $_POST['position'] );

		$url = add_query_arg(
			array(
				'BhRestToken' => self::$session,
			), self::$url . 'entity/JobSubmission'
		);

		$body = array(
			'candidate'       => array( 'id' => absint( $candidate->changedEntityId ) ),
			'jobOrder'        => array( 'id' => absint( $jobOrder ) ),
			'status'          => 'New Lead',
			'dateWebResponse' => self::microtime_float(), //date( 'u', $date ),// time(),
		);

		$response = wp_remote_get( $url, array( 'body' => json_encode( $body ), 'method' => 'PUT' ) );

		if ( 200 === $response['response']['code'] ) {
			return json_decode( $response['body'] );
		}

		return false;
	}

	/**
	 * get time in microseconds
	 * @return float
	 */
	private function microtime_float() {
		list( $usec, $sec ) = explode( ' ', microtime() );

		return ( (float) $usec + (float) $sec ) * 100;
	}
}

new Bullhorn_Extended_Connection();
Thanks,
Anne

SO-BHSupport
User
Posts: 24
Joined: Thu Feb 27, 2014 12:49 pm

Re: Help!: Error: Convert failed, resume mirror status: -1 - Rex has not been initialized 02244386

Post by SO-BHSupport » Tue May 24, 2016 9:23 am

Hi Annie,

I'm glad that resolved your previous issue! So for us to troubleshoot this we would need you to do the following:

Provide the REST URI that your making when trying to create the submission (like the example below).

PUT https://rest.bullhornstaffing.com/rest- ... Submission

{
"candidate": {"id": 3747},
"jobOrder": {"id": 36}
"status": "Submitted",
"dateWebResponse": 1370522348880
}

And ideally have this provided on a new forum post. By asking the same questions on the same forum post it can cause confusion and does not assist in other people finding solutions to their problems.

Kind regards,

Simon O'Keeffe
Simon O'Keeffe
Enterprise Support Team Lead
B U L L H O R N
Staffing and Recruiting Software, On Target, On Demand
617-478-9126 (US Support)
+44 800 032 2848 option 1 (UK Support)

Post Reply