Newer
Older
namespace Drupal\salesforce\Rest;
use Drupal\Component\Serialization\Json;

Aaron Bauman
committed
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;

git
committed
use Drupal\Core\Cache\CacheBackendInterface;

Aaron Bauman
committed
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\State\StateInterface;
use Drupal\salesforce\SelectQuery;
use Drupal\salesforce\SelectQueryResult;

Alexander Rhodes
committed
use Drupal\salesforce\SFID;
use Drupal\salesforce\SObject;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;

Aaron Bauman
committed
use GuzzleHttp\Psr7\Response;
/**
* Objects, properties, and methods to communicate with the Salesforce REST API.
*/
class RestClient implements RestClientInterface {
/**
* Reponse object.
*
* @var \GuzzleHttp\Psr7\Response
*/
/**
* GuzzleHttp client.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* Config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Salesforce API URL.
*
* @var Drupal\Core\Url
*/

git
committed
protected $url;
/**
* Salesforce config entity.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/

Aaron Bauman
committed
protected $config;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface

Aaron Bauman
committed
protected $state;
/**
* The cache service.
*
* @var Drupal\Core\Cache\CacheBackendInterface
protected $cache;
/**
* The JSON serializer service.
*
* @var \Drupal\Component\Serialization\Json
*/
protected $json;
const CACHE_LIFETIME = 300;

Aaron Bauman
committed
const LONGTERM_CACHE_LIFETIME = 86400;
/**
* Constructor which initializes the consumer.
* @param \GuzzleHttp\ClientInterface $http_client
* The GuzzleHttp Client.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Core\State\StateInterface $state
* The state service.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache service.
* @param \Drupal\Component\Serialization\Json $json
* The JSON serializer service.
public function __construct(ClientInterface $http_client, ConfigFactoryInterface $config_factory, StateInterface $state, CacheBackendInterface $cache, Json $json) {
$this->configFactory = $config_factory;
$this->httpClient = $http_client;
$this->config = $this->configFactory->getEditable('salesforce.settings');

Aaron Bauman
committed
$this->state = $state;
$this->cache = $cache;
$this->json = $json;
return $this;
}
/**
* Determine if this SF instance is fully configured.
*
* @TODO: Consider making a test API call.
*/
public function isAuthorized() {
return $this->getConsumerKey() && $this->getConsumerSecret() && $this->getRefreshToken();
}
/**
* {@inheritdoc}
public function apiCall($path, array $params = [], $method = 'GET', $returnObject = FALSE) {
if (!$this->getAccessToken()) {
$this->refreshToken();
}
try {
$this->response = new RestResponse($this->apiHttpRequest($path, $params, $method));
}
catch (RequestException $e) {
// RequestException gets thrown for any response status but 2XX.
$this->response = $e->getResponse();
// Any exceptions besides 401 get bubbled up.
if (!$this->response || $this->response->getStatusCode() != 401) {

Alexander Rhodes
committed
throw new RestException($this->response, $e->getMessage());
if ($this->response->getStatusCode() == 401) {
// The session ID or OAuth token used has expired or is invalid: refresh
// token. If refreshToken() throws an exception, or if apiHttpRequest()
// throws anything but a RequestException, let it bubble up.
$this->refreshToken();
try {
$this->response = new RestResponse($this->apiHttpRequest($path, $params, $method));
}
catch (RequestException $e) {
$this->response = $e->getResponse();

Alexander Rhodes
committed
throw new RestException($this->response, $e->getMessage());
if (empty($this->response)
|| ((int) floor($this->response->getStatusCode() / 100)) != 2) {

Alexander Rhodes
committed
throw new RestException($this->response, 'Unknown error occurred during API call');
return $this->response;
}
else {
return $this->response->data;
}
}
/**
* Private helper to issue an SF API request.
*
* @param string $path
* Path to resource.
* @param array $params
* Parameters to provide.
* @param string $method
* Method to initiate the call, such as GET or POST. Defaults to GET.
*

Aaron Bauman
committed
protected function apiHttpRequest($path, array $params, $method) {
if (!$this->getAccessToken()) {
}
$url = $this->getApiEndPoint() . $path;

Aaron Bauman
committed
$headers = [
'Authorization' => 'OAuth ' . $this->getAccessToken(),
'Content-type' => 'application/json',

Aaron Bauman
committed
];
$data = NULL;
if (!empty($params)) {
$data = $this->json->encode($params);
}
return $this->httpRequest($url, $data, $headers, $method);
}
/**
* Make the HTTP request. Wrapper around drupal_http_request().
*
* @param string $url
* Path to make request from.
* @param string $data
* The request body.
* @param array $headers
* Request headers to send as name => value.
* @param string $method
* Method to initiate the call, such as GET or POST. Defaults to GET.
*
* @throws RequestException
protected function httpRequest($url, $data = NULL, array $headers = [], $method = 'GET') {
// Build the request, including path and headers. Internal use.
return $this->httpClient->$method($url, ['headers' => $headers, 'body' => $data]);
/**
* Extract normalized error information from a RequestException.

git
committed
* @param RequestException $e
* @return array
* Error array with keys:
* * message
* * errorCode
* * fields
*/
protected function getErrorData(RequestException $e) {
$response = $e->getResponse();
$response_body = $response->getBody()->getContents();
$data = $this->json->decode($response_body);
if (!empty($data[0])) {
$data = $data[0];
}
return $data;
}
/**
* Get the API end point for a given type of the API.
*
* @param string $api_type
* E.g., rest, partner, enterprise.
*
* @return string
* Complete URL endpoint for API access.
*/
public function getApiEndPoint($api_type = 'rest') {
$url = &drupal_static(__FUNCTION__ . $api_type);
if (!isset($url)) {
$identity = $this->getIdentity();
if (is_string($identity)) {
$url = $identity;
}
elseif (isset($identity['urls'][$api_type])) {
$url = $identity['urls'][$api_type];
}

Aaron Bauman
committed
$url = str_replace('{version}', $this->getApiVersion(), $url);
}
return $url;
}

Aaron Bauman
committed
* Wrapper for config rest_api_version.version
*/
public function getApiVersion() {
if ($this->config->get('use_latest')) {
$versions = $this->getVersions();
$version = end($versions);

Aaron Bauman
committed
return $version['version'];
}
return $this->config->get('rest_api_version.version');
}
/**
* Setter for config salesforce.settings rest_api_version and use_latest
*
* @param bool $use_latest
* @param int $version
*/
public function setApiVersion($use_latest = TRUE, $version = NULL) {
if ($use_latest) {
$this->config->set('use_latest', $use_latest);
}
else {
$versions = $this->getVersions();
if (empty($versions[$version])) {
throw new Exception("Version $version is not available.");
}
$version = $versions[$version];
$this->config->set('rest_api_version', $version);
}
$this->config->save();
}

Aaron Bauman
committed
/**
* Getter for consumer_key
public function getConsumerKey() {

Aaron Bauman
committed
return $this->state->get('salesforce.consumer_key');

Aaron Bauman
committed
* Setter for consumer_key
public function setConsumerKey($value) {

Aaron Bauman
committed
return $this->state->set('salesforce.consumer_key', $value);
public function getConsumerSecret() {

Aaron Bauman
committed
return $this->state->get('salesforce.consumer_secret');
public function setConsumerSecret($value) {

Aaron Bauman
committed
return $this->state->set('salesforce.consumer_secret', $value);
}

Aaron Bauman
committed
public function getLoginUrl() {
$login_url = $this->state->get('salesforce.login_url');
return empty($login_url) ? 'https://login.salesforce.com' : $login_url;

Aaron Bauman
committed

Aaron Bauman
committed
public function setLoginUrl($value) {
return $this->state->set('salesforce.login_url', $value);
}
/**
* Get the SF instance URL. Useful for linking to objects.
*/
public function getInstanceUrl() {

Aaron Bauman
committed
return $this->state->get('salesforce.instance_url');
*
* @param string $url
* URL to set.
*/
protected function setInstanceUrl($url) {

Aaron Bauman
committed
$this->state->set('salesforce.instance_url', $url);
return $this;
}
/**
* Get the access token.
*/
public function getAccessToken() {

Aaron Bauman
committed
$access_token = $this->state->get('salesforce.access_token');
return isset($access_token) && Unicode::strlen($access_token) !== 0 ? $access_token : FALSE;
}
/**
* Set the access token.
*
* @param string $token
* Access token from Salesforce.
*/

Aaron Bauman
committed
public function setAccessToken($token) {
$this->state->set('salesforce.access_token', $token);
return $this;
}
/**
* Get refresh token.
*/
protected function getRefreshToken() {

Aaron Bauman
committed
return $this->state->get('salesforce.refresh_token');
}
/**
* Set refresh token.
*
* @param string $token
* Refresh token from Salesforce.
*/
protected function setRefreshToken($token) {

Aaron Bauman
committed
$this->state->set('salesforce.refresh_token', $token);
return $this;

Aaron Bauman
committed
* Refresh access token based on the refresh token.
* @throws Exception
*/
protected function refreshToken() {
$refresh_token = $this->getRefreshToken();
if (empty($refresh_token)) {

Aaron Bauman
committed
$data = UrlHelper::buildQuery([
'grant_type' => 'refresh_token',
'refresh_token' => urldecode($refresh_token),
'client_id' => $this->getConsumerKey(),
'client_secret' => $this->getConsumerSecret(),

Aaron Bauman
committed
]);

Aaron Bauman
committed
$url = $this->getAuthTokenUrl();
$headers = [
// This is an undocumented requirement on Salesforce's end.
'Content-Type' => 'application/x-www-form-urlencoded',

Aaron Bauman
committed
];
$response = $this->httpRequest($url, $data, $headers, 'POST');

Aaron Bauman
committed
$this->handleAuthResponse($response);
return $this;

Aaron Bauman
committed
}
/**
* Helper callback for OAuth handshake, and refreshToken()
*
* @param GuzzleHttp\Psr7\Response $response
* Response object from refreshToken or authToken endpoints.

Aaron Bauman
committed
*
* @see SalesforceController::oauthCallback()
* @see self::refreshToken()
*/
public function handleAuthResponse(Response $response) {
if ($response->getStatusCode() != 200) {
throw new \Exception($response->getReasonPhrase(), $response->getStatusCode());
$data = (new RestResponse($response))->data;
$this
->setAccessToken($data['access_token'])
->initializeIdentity($data['id'])
->setInstanceUrl($data['instance_url']);
// Do not overwrite an existing refresh token with an empty value.
if (!empty($data['refresh_token'])) {
$this->setRefreshToken($data['refresh_token']);
}
return $this;

Aaron Bauman
committed
}
/**
* Retrieve and store the Salesforce identity given an ID url.
*
* @param string $id
* Identity URL.
*
* @throws Exception

Aaron Bauman
committed
public function initializeIdentity($id) {
$headers = [
'Authorization' => 'OAuth ' . $this->getAccessToken(),
'Content-type' => 'application/json',

Aaron Bauman
committed
];
$response = $this->httpRequest($id, NULL, $headers);

Aaron Bauman
committed
if ($response->getStatusCode() != 200) {
throw new \Exception(t('Unable to access identity service.'), $response->getStatusCode());
$data = (new RestResponse($response))->data;

Aaron Bauman
committed
$this->setIdentity($data);
return $this;

Aaron Bauman
committed
}

Aaron Bauman
committed
* Setter for identity state info.

Aaron Bauman
committed
* @return $this

Aaron Bauman
committed
$this->state->set('salesforce.identity', $data);
return $this;
}
/**
* Return the Salesforce identity, which is stored in a variable.
*
* @return array
* Returns FALSE is no identity has been stored.
*/
public function getIdentity() {

Aaron Bauman
committed
return $this->state->get('salesforce.identity');

Aaron Bauman
committed
* Helper to build the redirect URL for OAUTH workflow.
*
* @return string
* Redirect URL.
*
* @see Drupal\salesforce\Controller\SalesforceController

Aaron Bauman
committed
public function getAuthCallbackUrl() {

Aaron Bauman
committed
'absolute' => TRUE,
'https' => TRUE,
])->toString();

Aaron Bauman
committed
* Get Salesforce oauth login endpoint. (OAuth step 1)

Aaron Bauman
committed
* @return string
* REST OAuth Login URL.

Aaron Bauman
committed
public function getAuthEndpointUrl() {
return $this->getLoginUrl() . '/services/oauth2/authorize';

Aaron Bauman
committed
* Get Salesforce oauth token endpoint. (OAuth step 2)
*
* @return string

Aaron Bauman
committed
* REST OAuth Token URL.

Aaron Bauman
committed
public function getAuthTokenUrl() {
return $this->getLoginUrl() . '/services/oauth2/token';

Aaron Bauman
committed
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
/**
* Wrapper for "Versions" resource to list information about API releases.
*
* @param $reset
* Whether to reset cache.
*
* @return array
* Array of all available Salesforce versions.
*/
public function getVersions($reset = FALSE) {
$cache = $this->cache->get('salesforce:versions');
// Force the recreation of the cache when it is older than 24 hours.
if ($cache && $this->getRequestTime() < ($cache->created + self::LONGTERM_CACHE_LIFETIME) && !$reset) {
return $cache->data;
}
$versions = [];
$id = $this->getIdentity();
$url = str_replace('v{version}/', '', $id['urls']['rest']);
$response = new RestResponse($this->httpRequest($url));
foreach ($response->data as $version) {
$versions[$version['version']] = $version;
}
$this->cache->set('salesforce:versions', $versions, 0, ['salesforce']);
return $versions;
}
/**
* @defgroup salesforce_apicalls Wrapper calls around core apiCall()
*/
/**
* Available objects and their metadata for your organization's data.
*
* @param array $conditions
* Associative array of filters to apply to the returned objects. Filters
* are applied after the list is returned from Salesforce.
* @param bool $reset
* Whether to reset the cache and retrieve a fresh version from Salesforce.
*
* @return array
* Available objects and metadata.
*
* @addtogroup salesforce_apicalls
*/

Aaron Bauman
committed
public function objects(array $conditions = ['updateable' => TRUE], $reset = FALSE) {
$cache = $this->cache->get('salesforce:objects');
// Force the recreation of the cache when it is older than 5 minutes.
if ($cache && $this->getRequestTime() < ($cache->created + self::CACHE_LIFETIME) && !$reset) {
$result = $cache->data;
}
else {
$result = $this->apiCall('sobjects');
$this->cache->set('salesforce:objects', $result, 0, ['salesforce']);
}
if (!empty($conditions)) {
foreach ($result['sobjects'] as $key => $object) {
foreach ($conditions as $condition => $value) {
if (!$object[$condition] == $value) {
unset($result['sobjects'][$key]);
}
}
}
}
return $result['sobjects'];
}
/**
* Use SOQL to get objects based on query string.
*
* The constructed SOQL query.
*
* @return SelectQueryResult
*
* @addtogroup salesforce_apicalls
*/

Aaron Bauman
committed
public function query(SelectQuery $query) {
// $this->moduleHandler->alter('salesforce_query', $query);
// Casting $query as a string calls SelectQuery::__toString().
return new SelectQueryResult($this->apiCall('query?q=' . (string) $query));
/**
* Given a select query result, fetch the next results set, if it exists.
*
* @param SelectQueryResult $results
* The query result which potentially has more records
* @return SelectQueryResult
* If there are no more results, $results->records will be empty.
*/
public function queryMore(SelectQueryResult $results) {
if ($results->done()) {
return new SelectQueryResult([
'totalSize' => $results->size(),
'done' => TRUE,
'records' => [],
]);
}
$version_path = parse_url($this->getApiEndPoint(), PHP_URL_PATH);
$next_records_url = str_replace($version_path, '', $results->nextRecordsUrl());
return new SelectQueryResult($this->apiCall($next_records_url));
}

Aaron Bauman
committed
* Retrieve all the metadata for an object.
*
* @param string $name
* Object type name, E.g., Contact, Account, etc.
* @param bool $reset
* Whether to reset the cache and retrieve a fresh version from Salesforce.
*
* @return RestResponse_Describe
* Salesforce object description object.
*
* @addtogroup salesforce_apicalls
*/
public function objectDescribe($name, $reset = FALSE) {
if (empty($name)) {
$cache = $this->cache->get('salesforce:object:' . $name);
// Force the recreation of the cache when it is older than 5 minutes.
if ($cache && $this->getRequestTime() < ($cache->created + self::CACHE_LIFETIME) && !$reset) {
return $cache->data;
}
else {
$response = new RestResponse_Describe($this->apiCall("sobjects/{$name}/describe", [], 'GET', TRUE));
$this->cache->set('salesforce:object:' . $name, $response, 0, ['salesforce']);
return $response;
}
}
/**
* Create a new object of the given type.
*
* @param string $name
* Object type name, E.g., Contact, Account, etc.
* @param array $params
* Values of the fields to set for the object.
*
* @return Drupal\salesforce\SFID
*
* @addtogroup salesforce_apicalls
*/

Aaron Bauman
committed
public function objectCreate($name, array $params) {
$response = $this->apiCall("sobjects/{$name}", $params, 'POST', TRUE);
$data = $response->data;
return new SFID($data['id']);
}
/**
* Create new records or update existing records.
*
* The new records or updated records are based on the value of the specified
* field. If the value is not unique, REST API returns a 300 response with

Aaron Bauman
committed
* the list of matching records and throws an Exception.
*
* @param string $name
* Object type name, E.g., Contact, Account.
* @param string $key
* The field to check if this record should be created or updated.
* @param string $value
* The value for this record of the field specified for $key.
* @param array $params
* Values of the fields to set for the object.
*
* @return mixed
* Drupal\salesforce\SFID or NULL.
*
* @addtogroup salesforce_apicalls
*/

Aaron Bauman
committed
public function objectUpsert($name, $key, $value, array $params) {
// If key is set, remove from $params to avoid UPSERT errors.
if (isset($params[$key])) {
unset($params[$key]);
}

Aaron Bauman
committed
$response = $this->apiCall("sobjects/{$name}/{$key}/{$value}", $params, 'PATCH', TRUE);

Aaron Bauman
committed
// On update, upsert method returns an empty body. Retreive object id, so that we can return a consistent response.
if ($response->getStatusCode() == 204) {
// We need a way to allow callers to distinguish updates and inserts. To
// that end, cache the original response and reset it after fetching the
// ID.
$this->original_response = $response;

Aaron Bauman
committed
$sf_object = $this->objectReadbyExternalId($name, $key, $value);
return $sf_object->id();

Aaron Bauman
committed
}
$data = $response->data;
return new SFID($data['id']);
}
/**
* Update an existing object.
*
* Update() doesn't return any data. Examine HTTP response or Exception.
*
* @param string $name
* Object type name, E.g., Contact, Account.
* @param string $id
* Salesforce id of the object.
* @param array $params
* Values of the fields to set for the object.
*
* @addtogroup salesforce_apicalls
*/

Aaron Bauman
committed
public function objectUpdate($name, $id, array $params) {
$this->apiCall("sobjects/{$name}/{$id}", $params, 'PATCH');
}
/**
* Return a full loaded Salesforce object.
*
* @param string $name
* Object type name, E.g., Contact, Account.
* @param string $id
* Salesforce id of the object.
*
* @return SObject
* Object of the requested Salesforce object.
*
* @addtogroup salesforce_apicalls
*/
public function objectRead($name, $id) {
return new SObject($this->apiCall("sobjects/{$name}/{$id}"));

Aaron Bauman
committed
* Return a full loaded Salesforce object from External ID.
*
* @param string $name

Aaron Bauman
committed
* Object type name, E.g., Contact, Account.
* @param string $field
* Salesforce external id field name.
* @param string $value
* Value of external id.
* @return SObject

Aaron Bauman
committed
* Object of the requested Salesforce object.
*
* @addtogroup salesforce_apicalls
*/

Aaron Bauman
committed
public function objectReadbyExternalId($name, $field, $value) {
return new SObject($this->apiCall("sobjects/{$name}/{$field}/{$value}"));
* Delete a Salesforce object.
*
* Note: if Object with given $id doesn't exist,
* objectDelete() will assume success unless $throw_exception is given.
* Delete() doesn't return any data. Examine HTTP response or Exception.
*
* @param string $name
* Object type name, E.g., Contact, Account.
* @param string $id
* Salesforce id of the object.
* @param bool $throw_exception
* (optional) If TRUE, 404 response code will cause RequestException to be
* thrown. Otherwise, hide those errors. Default is FALSE.
*
* @addtogroup salesforce_apicalls
*/
public function objectDelete($name, $id, $throw_exception = FALSE) {
try {
$this->apiCall("sobjects/{$name}/{$id}", [], 'DELETE');
}
catch (RequestException $e) {
if ($throw_exception || $e->getResponse()->getStatusCode() != 404) {
throw $e;
}
}
/**
* Retrieves the list of individual objects that have been deleted within the
* given timespan for a specified object type.
*
* @param string $type
* Object type name, E.g., Contact, Account.
* @param string $startDate
* Start date to check for deleted objects (in ISO 8601 format).
* @param string $endDate
* End date to check for deleted objects (in ISO 8601 format).
* @return GetDeletedResult
*/
public function getDeleted($type, $startDate, $endDate) {
return $this->apiCall("sobjects/{$type}/deleted/?start={$startDate}&end={$endDate}");
}
/**
* Return a list of available resources for the configured API version.
*
* @return Drupal\salesforce\Rest\RestResponse_Resources
*
* @addtogroup salesforce_apicalls
*/
public function listResources() {
return new RestResponse_Resources($this->apiCall('', [], 'GET', TRUE));
}
/**
* Return a list of SFIDs for the given object, which have been created or
* updated in the given timeframe.
*
* @param string $name
* Object type name, E.g., Contact, Account.
* @param int $start
* Unix timestamp for older timeframe for updates.
* Defaults to "-29 days" if empty.
* @param int $end
* Unix timestamp for end of timeframe for updates.
* Defaults to now if empty.
*
* @return array
* return array has 2 indexes:
* "ids": a list of SFIDs of those records which have been created or
* updated in the given timeframe.
* "latestDateCovered": ISO 8601 format timestamp (UTC) of the last date
* covered in the request.
*
* @see https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_getupdated.htm
*
* @addtogroup salesforce_apicalls
*/
public function getUpdated($name, $start = NULL, $end = NULL) {
if (empty($start)) {
$start = strtotime('-29 days');
$start = urlencode(gmdate(DATE_ATOM, $start));

git
committed
if (empty($end)) {
$end = time();
}
$end = urlencode(gmdate(DATE_ATOM, $end));

git
committed
return $this->apiCall("sobjects/{$name}/updated/?start=$start&end=$end");
/**
* Retrieve all record types for this org. If $name is provided, retrieve
* record types for the given object type only.
*
* @param string $name
* Object type name, e.g. Contact, Account, etc.
* @return array
* If $name is given, an array of record types indexed by developer name.
* Otherwise, an array of record type arrays, indexed by object type name.
*/
public function getRecordTypes($name = NULL, $reset = FALSE) {
$cache = $this->cache->get('salesforce:record_types');
// Force the recreation of the cache when it is older than CACHE_LIFETIME
if ($cache && $this->getRequestTime() < ($cache->created + self::CACHE_LIFETIME) && !$reset) {
$record_types = $cache->data;
}
else {
$query->fields = array('Id', 'Name', 'DeveloperName', 'SobjectType');
$result = $this->query($query);
$record_types = array();
foreach ($result->records() as $rt) {
$record_types[$rt->field('SobjectType')][$rt->field('DeveloperName')] = $rt;
$this->cache->set('salesforce:record_types', $record_types, 0, ['salesforce']);
if ($name != NULL) {
if (!isset($record_types[$name])) {
throw new \Exception("No record types for $name");
}
return $record_types[$name];
}
return $record_types;
}
/**
* Given a DeveloperName and SObject Name, return the SFID of the
* corresponding RecordType. DeveloperName doesn't change between Salesforce
* environments, so it's safer to rely on compared to SFID.
*
* @param string $name
* Object type name, E.g., Contact, Account.

git
committed
* @param string $devname
* RecordType DeveloperName, e.g. Donation, Membership, etc.
* @return SFID
* The Salesforce ID of the given Record Type, or null.
*
* @throws Exception if record type not found
*/
public function getRecordTypeIdByDeveloperName($name, $devname, $reset = FALSE) {
$record_types = $this->getRecordTypes();
if (empty($record_types[$name][$devname])) {
throw new \Exception("No record type $devname for $name");
}
return $record_types[$name][$devname]->id();
}
/**
* Utility function to determine object type for given SFID.
*

git
committed
* @param SFID $id
* Salesforce object ID.
*
* @return string
* Object type's name.
*
* @throws Exception
* If SFID doesn't match any object type.
*/

Aaron Bauman
committed
public function getObjectTypeName(SFID $id) {
$prefix = substr((string)$id, 0, 3);
$describe = $this->objects();
foreach ($describe as $object) {
if ($prefix == $object['keyPrefix']) {
return $object['name'];
}
}
}
/**
* Returns REQUEST_TIME.
*
* @return string
* The REQUEST_TIME server variable.
*/
protected function getRequestTime() {
return defined('REQUEST_TIME') ? REQUEST_TIME : (int) $_SERVER['REQUEST_TIME'];
}