Skip to content
Snippets Groups Projects
Commit 78fa84ae authored by Aaron Bauman's avatar Aaron Bauman
Browse files

- Include D7 drush commands and spruce them up a bit, plus minor changes to...

- Include D7 drush commands and spruce them up a bit, plus minor changes to associated dependencies in SFID, RestClient, and the REST SDK.
- Adds "use latest REST API version by default" boolean to settings, and enables it via hook_update
parent 3af5232f
No related branches found
No related tags found
No related merge requests found
rest_api_version: rest_api_version:
label: "Summer '16" label: ""
url: "/services/data/v37.0/" url: ""
version: "37.0" version: "39.0"
\ No newline at end of file use_latest: true
\ No newline at end of file
...@@ -2,6 +2,9 @@ salesforce.settings: ...@@ -2,6 +2,9 @@ salesforce.settings:
type: config_object type: config_object
label: 'Salesforce Settings' label: 'Salesforce Settings'
mapping: mapping:
use_latest:
type: boolean
label: 'Use latest REST API Version by default'
rest_api_version: rest_api_version:
type: mapping type: mapping
label: 'REST API Version' label: 'REST API Version'
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
* Drush integration for Salesforce. * Drush integration for Salesforce.
*/ */
use Drupal\salesforce\SFID;
use Drupal\salesforce\SelectQuery;
/** /**
* Implements hook_drush_command(). * Implements hook_drush_command().
*/ */
...@@ -13,10 +16,12 @@ function salesforce_drush_command() { ...@@ -13,10 +16,12 @@ function salesforce_drush_command() {
'description' => 'Displays information about the current REST API version', 'description' => 'Displays information about the current REST API version',
'aliases' => ['sfrv'], 'aliases' => ['sfrv'],
]; ];
$items['sf-list-objects'] = [ $items['sf-list-objects'] = [
'description' => 'List the objects that are available in your organization and available to the logged-in user.', 'description' => 'List the objects that are available in your organization and available to the logged-in user.',
'aliases' => ['sflo'], 'aliases' => ['sflo'],
]; ];
$items['sf-describe-object'] = [ $items['sf-describe-object'] = [
'description' => 'Retrieve all the metadata for an object, including information about each field, URLs, and child relationships.', 'description' => 'Retrieve all the metadata for an object, including information about each field, URLs, and child relationships.',
'aliases' => ['sfdo'], 'aliases' => ['sfdo'],
...@@ -24,14 +29,96 @@ function salesforce_drush_command() { ...@@ -24,14 +29,96 @@ function salesforce_drush_command() {
'object' => 'The object name in Salesforce.', 'object' => 'The object name in Salesforce.',
], ],
'options' => [ 'options' => [
'fields' => 'Display information about fields that are part of the object.', 'output' => "Specify an output type.
'field-data' => 'Display information about a specific field that is part of an object', Options are:
info: (default) Display metadata about an object
fields: Display information about fields that are part of the object
field-data FIELDNAME: Display information about a specific field that is part of an object
raw: Display the complete, raw describe response."
], ],
'examples' => [
'drush sfdo Contact' => 'Show metadata about Contact SObject type.',
'drush sfdo Contact --output=fields' => 'Show addtional metadata about Contact fields.',
'drush sfdo Contact --output=field --field=Email' => 'Show full metadata about Contact.Email field.',
'drush sfdo Contact --output=raw' => 'Display the full metadata for Contact SObject type.'
]
]; ];
$items['sf-list-resources'] = [ $items['sf-list-resources'] = [
'description' => 'Lists the resources available for the specified API version. It provides the name and URI of each resource.', 'description' => 'Lists the resources available for the specified API version. It provides the name and URI of each resource.',
'aliases' => ['sflr'], 'aliases' => ['sflr'],
]; ];
$items['sf-read-object'] = array(
'description' => 'Retrieve all the data for an object with a specific ID.',
'aliases' => array('sfro'),
'arguments' => array(
'id' => 'The object ID in Salesforce.',
),
'options' => array(
'format' => array(
'description' => 'Format to output the object. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON.',
'example-value' => 'export',
),
),
);
$items['sf-create-object'] = array(
'description' => 'Create an object with specified data.',
'aliases' => array('sfco'),
'arguments' => array(
'object' => 'The object type name in Salesforce (e.g. Account).',
'data' => 'The data to use when creating the object (default is JSON format). Use \'-\' to read the data from STDIN.',
),
'options' => array(
'format' => array(
'description' => 'Format to parse the object. Use "json" for JSON (default) or "query" for data formatted like a query string, e.g. \'Company=Foo&LastName=Bar\'.',
'example-value' => 'json',
),
),
);
$items['sf-query-object'] = array(
'description' => 'Query an object using SOQL with specified conditions.',
'aliases' => array('sfqo'),
'arguments' => array(
'object' => 'The object type name in Salesforce (e.g. Account).',
),
'options' => array(
'format' => array(
'description' => 'Format to output the objects. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON.',
'example-value' => 'export',
),
'where' =>array(
'description' => 'A WHERE clause to add to the SOQL query',
'example-value' => 'isDeleted = TRUE',
),
'fields' =>array(
'description' => 'A comma-separated list fields to select in the SOQL query. If absent, an API call is used to find all fields',
'example-value' => 'Id,LastName',
),
'limit' => [
'description' => 'Integer limit on the number of results to return for the query.',
],
'order' => [
'description' => 'Comma-separated fields by which to sort results. Make sure to enclose in quotes for any whitespace.',
'example-value' => '"Name, SystemModStamp DESC"',
],
),
);
$items['sf-execute-query'] = array(
'description' => 'Execute a SOQL query.',
'aliases' => array('sfeq', 'soql'),
'arguments' => array(
'query' => 'The query to execute.',
),
);
$items['sf-pull'] = [
'description' => 'Pull (import) records for from Salesforce, or enqueue them to be pulled.',
'aliases' => ['sfpull', 'sfi'],
];
return $items; return $items;
} }
...@@ -41,7 +128,7 @@ function salesforce_drush_command() { ...@@ -41,7 +128,7 @@ function salesforce_drush_command() {
* This command provides the name and URI of each resource. * This command provides the name and URI of each resource.
*/ */
function drush_salesforce_sf_list_resources() { function drush_salesforce_sf_list_resources() {
$salesforce = _drush_salesforce_drush_get_api(); $salesforce = \Drupal::service('salesforce.client');
$resources = $salesforce->listResources(); $resources = $salesforce->listResources();
if ($resources) { if ($resources) {
$items[] = ['Resource', 'URL']; $items[] = ['Resource', 'URL'];
...@@ -71,101 +158,89 @@ function drush_salesforce_sf_describe_object($object_name = NULL) { ...@@ -71,101 +158,89 @@ function drush_salesforce_sf_describe_object($object_name = NULL) {
if (!$object_name) { if (!$object_name) {
return drush_log('Please specify an object as an argument.', 'error'); return drush_log('Please specify an object as an argument.', 'error');
} }
$salesforce = _drush_salesforce_drush_get_api(); $salesforce = \Drupal::service('salesforce.client');
$object = $salesforce->objectDescribe($object_name); $object = $salesforce->objectDescribe($object_name);
// Return if we cannot load any data. // Return if we cannot load any data.
if (!is_array($object)) { if (!is_object($object)) {
return drush_log(dt('Could not load data for object !object', ['!object' => $object]), 'error'); return drush_log(dt('Could not load data for object !object', ['!object' => $object_name]), 'error');
}
// Display only information about fields for an option.
if (drush_get_option('fields')) {
$rows = [['Name', 'Type', 'Label']];
foreach ($object['fields'] as $field) {
$rows[] = [$field['name'], $field['type'], $field['label']];
}
drush_print_r($rows);
drush_print_table($rows, TRUE);
return;
} }
// Display only information about a specific field. $output = drush_get_option('output');
if ($fieldname = drush_get_option('field-data')) { switch ($output) {
$field_data = NULL; case 'raw':
foreach ($object['fields'] as $field) { drush_print_r($object->data);
if ($field['name'] === $fieldname) { return;
$field_data = $field; case 'fields':
break; $rows = [['Name', 'Type', 'Label']];
foreach ($object->fields as $field) {
$rows[] = [$field['name'], $field['type'], $field['label']];
} }
} drush_print_table($rows, TRUE);
if (!$field_data) { return;
drush_log(dt('Could not load data for field !field on !object object', [ case 'field':
'!field' => $fieldname, $fieldname = drush_get_option('field');
'!object' => $object_name, if (empty($fieldname)) {
]), 'error'); drush_log(dt('Please specify a field name'), 'error');
} return;
else { }
drush_print_r($field); try {
} $field_data = $object->getField($fieldname);
}
catch (\Exception $e) {
watchdog_exception('salesforce.drush', $e);
drush_log(dt('Could not load data for field !field on !object object', [
'!field' => $fieldname,
'!object' => $object_name,
]), 'error');
return;
}
drush_print_r($field_data);
return; default:
} if ($output != 'info') {
drush_log(dt('Unkonwn output option !output', ['!output' => $output]), 'error');
return;
}
// Display information about the object. // Display information about the object.
// @TODO add remaining field objects? $rows = [];
$rows = []; $rows[] = ['Name', $object->name];
$rows[] = ['Name', $object['name']]; $rows[] = ['Label', $object->label];
$rows[] = [ $rows[] = ['Field count', count($object->getFields())];
'Fields', $rows[] = ['SFID prefix', $object->keyPrefix];
isset($object['fields']) ? count($object['fields']) : 0, $rows[] = [
]; 'Child Relationships',
$rows[] = [ isset($object->childRelationships) ? count($object->childRelationships) : 0,
'Child Relationships', ];
isset($object['childRelationships']) ? count($object['childRelationships']) : 0,
]; $rows[] = ['Searchable', ($object->searchable == 1) ? 'TRUE' : 'FALSE'];
$rows[] = [ $rows[] = ['Creatable', ($object->createable == 1) ? 'TRUE' : 'FALSE'];
'Searchable', $rows[] = ['Deletable', ($object->deletable == 1) ? 'TRUE' : 'FALSE'];
($object['searchable'] == 1) ? 'TRUE' : 'FALSE', $rows[] = ['Mergeable', ($object->mergeable == 1) ? 'TRUE' : 'FALSE'];
]; $rows[] = ['Queryable', ($object->queryable == 1) ? 'TRUE' : 'FALSE'];
$rows[] = ['Creatable', ($object['createable'] == 1) ? 'TRUE' : 'FALSE']; drush_print_table($rows);
$rows[] = ['Deletable', ($object['deletable'] == 1) ? 'TRUE' : 'FALSE']; return;
$rows[] = ['Mergeable', ($object['mergeable'] == 1) ? 'TRUE' : 'FALSE']; }
$rows[] = ['Queryable', ($object['queryable'] == 1) ? 'TRUE' : 'FALSE'];
drush_print_table($rows);
} }
/** /**
* Displays information about the REST API version the site is using. * Displays information about the REST API version the site is using.
*/ */
function drush_salesforce_sf_rest_version() { function drush_salesforce_sf_rest_version() {
$salesforce = _drush_salesforce_drush_get_api(); $salesforce = \Drupal::service('salesforce.client');
if (isset($salesforce->rest_api_version)) { $version_id = $salesforce->getApiVersion();
$rows[] = ['Salesforce', 'Value']; $versions = $salesforce->getVersions();
foreach ($salesforce->rest_api_version as $key => $value) { $version = $versions[$version_id];
$rows[] = [$key, $value]; $latest = array_pop($versions);
}
$rows[] = ['login url', $salesforce->login_url];
drush_print_table($rows, TRUE);
}
else {
drush_log('Could not obtain information about the current REST API version!', 'error');
}
}
/** foreach ($version as $key => $value) {
* Wrapper around salesforce_get_api(). $rows[] = [$key, $value];
*
* If salesforce_get_api() does not return a connection to Salesforce,
* this function can prompt the user for username/password to obtain a new
* token.
*
* @TODO implement this function
*/
function _drush_salesforce_drush_get_api() {
if ($salesforce = \Drupal::service('salesforce.client')) {
return $salesforce;
} }
$rows[] = ['login url', $salesforce->getLoginUrl()];
$rows[] = ['latest version', strcmp($version_id, $latest['version']) ? $latest['version'] : 'Yes'];
drush_print_table($rows, TRUE);
} }
/** /**
...@@ -175,7 +250,7 @@ function _drush_salesforce_drush_get_api() { ...@@ -175,7 +250,7 @@ function _drush_salesforce_drush_get_api() {
* and available to the logged-in user. * and available to the logged-in user.
*/ */
function drush_salesforce_sf_list_objects() { function drush_salesforce_sf_list_objects() {
$salesforce = _drush_salesforce_drush_get_api(); $salesforce = \Drupal::service('salesforce.client');
if ($objects = $salesforce->objects()) { if ($objects = $salesforce->objects()) {
drush_print('The following objects are available in your organization and available to the logged-in user.'); drush_print('The following objects are available in your organization and available to the logged-in user.');
$rows[] = ['Name', 'Label', 'Label Plural']; $rows[] = ['Name', 'Label', 'Label Plural'];
...@@ -193,3 +268,141 @@ function drush_salesforce_sf_list_objects() { ...@@ -193,3 +268,141 @@ function drush_salesforce_sf_list_objects() {
} }
} }
/**
* Read a Salesforce object available to the logged-in user.
*
* @param $name
* The object type name, e.g. Account
* @param $id
* The Salesforce ID
*/
function drush_salesforce_sf_read_object($id) {
$salesforce = \Drupal::service('salesforce.client');
try {
$name = $salesforce->getObjectTypeName(new SFID($id));
if ($object = $salesforce->objectRead($name, $id)) {
drush_print(dt('!type with id !id:', ['!type' => $object->type(), '!id' => $object->id()]));
drush_print(drush_format($object->fields()));
}
}
catch (SalesforceException $e) {
drush_log($e->getMessage(), 'error');
}
}
/**
* Create a Salesforce object available to the logged-in user.
*
* @param $name
* The object type name, e.g. Account
* @param $data
* The object data, or '-' to read from stdin
*/
function drush_salesforce_sf_create_object($name, $data) {
if ($data == '-') {
$data = stream_get_contents(STDIN);
}
$format = drush_get_option('format', 'json');
$params = array();
switch ($format) {
case 'query':
parse_str($data, $params);
break;
case 'json':
$params = json_decode($data, TRUE);
break;
default:
drush_log(dt('Invalid format'), 'error');
return;
}
$salesforce = \Drupal::service('salesforce.client');
try {
if ($result = $salesforce->objectCreate($name, $params)) {
drush_print_r($result);
}
}
catch (SalesforceException $e) {
drush_log($e->getMessage(), 'error');
}
}
/**
* Query Salesforce objects available to the logged-in user.
*
* @param $name
* The object type name, e.g. Account
*/
function drush_salesforce_sf_query_object($name) {
$salesforce = \Drupal::service('salesforce.client');
$query = new SelectQuery($name);
$fields = drush_get_option('fields', '');
if (!$fields) {
$object = $salesforce->objectDescribe($name);
$query->fields = array_keys($object->getFields());
}
else {
$query->fields = explode(',', $fields);
}
$query->limit = drush_get_option('limit', '');
if ($where = drush_get_option('where', '')) {
$query->conditions = [[$where]];
}
if ($order = drush_get_option('order', '')) {
$query->order = [];
$orders = explode(',', $order);
foreach ($orders as $order) {
list($field, $dir) = preg_split('/\s+/', $order, 2);
$query->order[$field] = $dir;
}
}
try {
$result = $salesforce->query($query);
}
catch (SalesforceException $e) {
drush_log($e->getMessage(), 'error');
return;
}
foreach ($result->records() as $sfid => $record) {
drush_print(drush_format([$sfid => $record->fields()]));
}
$pretty_query = str_replace('+', ' ', (string)$query);
if (!$fields) {
$fields = implode(',', $query->fields);
$pretty_query = str_replace($fields, ' * ', $pretty_query);
}
drush_print(dt("Showing !size of !total records for query:\n!query", ['!size' => count($result->records()), '!total' => $result->size(), '!query' => $pretty_query]));
}
/**
* Execute a SOQL query.
*
* @param $query
* The query to execute
*/
function drush_salesforce_sf_execute_query($query = NULL) {
if (!$query) {
return drush_log('Please specify a query as an argument.', 'error');
}
$salesforce = \Drupal::service('salesforce.client');
try {
$result = $salesforce->apiCall('query?q=' . urlencode($query));
drush_print(drush_format($result));
}
catch (SalesforceException $e) {
drush_log($e->getMessage(), 'error');
}
}
function drush_salesforce_sf_pull() {
}
...@@ -17,3 +17,11 @@ function salesforce_uninstall() { ...@@ -17,3 +17,11 @@ function salesforce_uninstall() {
\Drupal::state()->deleteMultiple($delete); \Drupal::state()->deleteMultiple($delete);
} }
/**
* Install new "Use Latest API version" boolean; defaults to TRUE.
*/
function salesforce_update_8001() {
$settings = \Drupal::configFactory()->getEditable('salesforce.settings');
$settings->set('use_latest', TRUE);
$settings->save();
}
...@@ -26,12 +26,12 @@ class RestClient implements RestClientInterface { ...@@ -26,12 +26,12 @@ class RestClient implements RestClientInterface {
protected $httpClient; protected $httpClient;
protected $configFactory; protected $configFactory;
protected $url; protected $url;
private $config; protected $config;
private $configEditable; protected $state;
private $state;
protected $cache; protected $cache;
const CACHE_LIFETIME = 300; const CACHE_LIFETIME = 300;
const LONGTERM_CACHE_LIFETIME = 86400;
/** /**
* Constructor which initializes the consumer. * Constructor which initializes the consumer.
...@@ -45,7 +45,6 @@ class RestClient implements RestClientInterface { ...@@ -45,7 +45,6 @@ class RestClient implements RestClientInterface {
$this->configFactory = $config_factory; $this->configFactory = $config_factory;
$this->httpClient = $http_client; $this->httpClient = $http_client;
$this->config = $this->configFactory->get('salesforce.settings'); $this->config = $this->configFactory->get('salesforce.settings');
$this->configEditable = $this->configFactory->getEditable('salesforce.settings');
$this->state = $state; $this->state = $state;
$this->cache = $cache; $this->cache = $cache;
} }
...@@ -213,20 +212,31 @@ class RestClient implements RestClientInterface { ...@@ -213,20 +212,31 @@ class RestClient implements RestClientInterface {
elseif (isset($identity['urls'][$api_type])) { elseif (isset($identity['urls'][$api_type])) {
$url = $identity['urls'][$api_type]; $url = $identity['urls'][$api_type];
} }
$url = str_replace('{version}', $this->config->get('rest_api_version.version'), $url); $url = str_replace('{version}', $this->getApiVersion(), $url);
} }
return $url; return $url;
} }
/** /**
* * Wrapper for config rest_api_version.version
*/
public function getApiVersion() {
if ($this->config->get('use_latest')) {
$version = end($this->getVersions());
return $version['version'];
}
return $this->config->get('rest_api_version.version');
}
/**
* Getter for consumer_key
*/ */
public function getConsumerKey() { public function getConsumerKey() {
return $this->state->get('salesforce.consumer_key'); return $this->state->get('salesforce.consumer_key');
} }
/** /**
* * Setter for consumer_key
*/ */
public function setConsumerKey($value) { public function setConsumerKey($value) {
return $this->state->set('salesforce.consumer_key', $value); return $this->state->set('salesforce.consumer_key', $value);
...@@ -398,7 +408,9 @@ class RestClient implements RestClientInterface { ...@@ -398,7 +408,9 @@ class RestClient implements RestClientInterface {
} }
/** /**
* Setter for identity state info.
* *
* @return $this
*/ */
protected function setIdentity($data) { protected function setIdentity($data) {
$this->state->set('salesforce.identity', $data); $this->state->set('salesforce.identity', $data);
...@@ -450,6 +462,34 @@ class RestClient implements RestClientInterface { ...@@ -450,6 +462,34 @@ class RestClient implements RestClientInterface {
return $this->getLoginUrl() . '/services/oauth2/token'; return $this->getLoginUrl() . '/services/oauth2/token';
} }
/**
* 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() * @defgroup salesforce_apicalls Wrapper calls around core apiCall()
*/ */
...@@ -816,7 +856,7 @@ class RestClient implements RestClientInterface { ...@@ -816,7 +856,7 @@ class RestClient implements RestClientInterface {
* @return string * @return string
* @throws Exception if SFID doesn't match any object type * @throws Exception if SFID doesn't match any object type
*/ */
public static function getObjectTypeName(SFID $id) { public function getObjectTypeName(SFID $id) {
$prefix = substr((string)$id, 0, 3); $prefix = substr((string)$id, 0, 3);
$describe = $this->objects(); $describe = $this->objects();
foreach ($describe as $object) { foreach ($describe as $object) {
......
...@@ -403,6 +403,6 @@ interface RestClientInterface { ...@@ -403,6 +403,6 @@ interface RestClientInterface {
* @return string * @return string
* @throws Exception if SFID doesn't match any object type * @throws Exception if SFID doesn't match any object type
*/ */
public static function getObjectTypeName(SFID $id); public function getObjectTypeName(SFID $id);
} }
...@@ -19,7 +19,7 @@ class RestResponse_Describe extends RestResponse { ...@@ -19,7 +19,7 @@ class RestResponse_Describe extends RestResponse {
protected $name; protected $name;
/** /**
* Flattened fields mapping field name => field label * Flattened fields mapping field name => field label
* *
* @var array * @var array
*/ */
...@@ -28,13 +28,13 @@ class RestResponse_Describe extends RestResponse { ...@@ -28,13 +28,13 @@ class RestResponse_Describe extends RestResponse {
/** /**
* See https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_describe.htm * See https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_describe.htm
* *
* @param RestResponse $response * @param RestResponse $response
*/ */
public function __construct(RestResponse $response) { public function __construct(RestResponse $response) {
parent::__construct($response->response); parent::__construct($response->response);
$this->name = $response->data['name']; $this->name = $response->data['name'];
$this->fields = [];
// Index fields by machine name, so we don't have to search every time. // Index fields by machine name, so we don't have to search every time.
foreach ($response->data['fields'] as $field) { foreach ($response->data['fields'] as $field) {
$this->fields[$field['name']] = $field; $this->fields[$field['name']] = $field;
...@@ -46,6 +46,7 @@ class RestResponse_Describe extends RestResponse { ...@@ -46,6 +46,7 @@ class RestResponse_Describe extends RestResponse {
} }
$this->$key = $value; $this->$key = $value;
} }
$this->data = $response->data;
} }
/** /**
......
...@@ -9,7 +9,7 @@ class SFID { ...@@ -9,7 +9,7 @@ class SFID {
public function __construct($id) { public function __construct($id) {
if (strlen($id) != 15 && strlen($id) != self::MAX_LENGTH) { if (strlen($id) != 15 && strlen($id) != self::MAX_LENGTH) {
throw new \Exception('Invalid sfid'); throw new \Exception('Invalid sfid ' . strlen($id));
} }
$this->id = $id; $this->id = $id;
if (strlen($this->id) == 15) { if (strlen($this->id) == 15) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment