247 lines
8.6 KiB
PHP
Executable File
247 lines
8.6 KiB
PHP
Executable File
<?php
|
|
|
|
namespace MauticPlugin\MauticCrmBundle\Api;
|
|
|
|
use Mautic\LeadBundle\Entity\Lead;
|
|
use Mautic\PluginBundle\Exception\ApiErrorException;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
|
|
class DynamicsApi extends CrmApi
|
|
{
|
|
private function getUrl(): string
|
|
{
|
|
$keys = $this->integration->getKeys();
|
|
|
|
return $keys['resource'].'/api/data/v8.2';
|
|
}
|
|
|
|
/**
|
|
* @param string $method
|
|
* @param string $moduleobject
|
|
*
|
|
* @return array|ResponseInterface
|
|
*
|
|
* @throws ApiErrorException
|
|
*/
|
|
protected function request($operation, array $parameters = [], $method = 'GET', $moduleobject = 'contacts', $settings = [])
|
|
{
|
|
if ('company' === $moduleobject) {
|
|
$moduleobject = 'accounts';
|
|
}
|
|
|
|
if ('' === $operation) {
|
|
$operation = $moduleobject;
|
|
}
|
|
|
|
$url = sprintf('%s/%s', $this->getUrl(), $operation);
|
|
|
|
if (isset($parameters['request_settings'])) {
|
|
$settings = array_merge($settings, $parameters['request_settings']);
|
|
unset($parameters['request_settings']);
|
|
}
|
|
|
|
$settings = array_merge($settings, [
|
|
'encode_parameters' => 'json',
|
|
'return_raw' => 'true', // needed to get the HTTP status code in the response
|
|
'request_timeout' => 300,
|
|
]);
|
|
|
|
/** @var ResponseInterface $response */
|
|
$response = $this->integration->makeRequest($url, $parameters, $method, $settings);
|
|
|
|
if ('POST' === $method && (!($response instanceof ResponseInterface) || !in_array($response->getStatusCode(), [200, 204], true))) {
|
|
throw new ApiErrorException('Dynamics CRM API error: '.json_encode($response->getBody()));
|
|
}
|
|
|
|
if ('GET' === $method && $response instanceof ResponseInterface) {
|
|
return json_decode($response->getBody(), true);
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* List types.
|
|
*
|
|
* @param string $object Zoho module name
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getLeadFields($object = 'contacts')
|
|
{
|
|
if ('company' === $object) {
|
|
$object = 'accounts'; // Dynamics object name
|
|
}
|
|
|
|
$logicalName = rtrim($object, 's'); // singularize object name
|
|
|
|
$operation = sprintf('EntityDefinitions(LogicalName=\'%s\')/Attributes', $logicalName);
|
|
$parameters = [
|
|
'$filter' => 'Microsoft.Dynamics.CRM.NotIn(PropertyName=\'AttributeTypeName\',PropertyValues=["Virtual", "Uniqueidentifier", "Picklist", "Lookup", "Owner", "Customer"]) and IsValidForUpdate eq true and AttributeOf eq null and LogicalName ne \'parentcustomerid\'', // ignore system fields
|
|
'$select' => 'RequiredLevel,LogicalName,DisplayName,AttributeTypeName', // select only miningful columns
|
|
];
|
|
|
|
return $this->request($operation, $parameters, 'GET', $object);
|
|
}
|
|
|
|
/**
|
|
* @param Lead $lead
|
|
*/
|
|
public function createLead($data, $lead, $object = 'contacts'): ResponseInterface
|
|
{
|
|
return $this->request('', $data, 'POST', $object);
|
|
}
|
|
|
|
public function updateLead($data, $objectId): ResponseInterface
|
|
{
|
|
// $settings['headers']['If-Match'] = '*'; // prevent create new contact
|
|
return $this->request(sprintf('contacts(%s)', $objectId), $data, 'PATCH', 'contacts', []);
|
|
}
|
|
|
|
/**
|
|
* gets leads.
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getLeads(array $params)
|
|
{
|
|
return $this->request('', $params, 'GET', 'contacts');
|
|
}
|
|
|
|
/**
|
|
* gets companies.
|
|
*
|
|
* @param string $id
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getCompanies(array $params, $id = null)
|
|
{
|
|
if ($id) {
|
|
$operation = sprintf('accounts(%s)', $id);
|
|
$data = $this->request($operation, $params, 'GET');
|
|
} else {
|
|
$data = $this->request('', $params, 'GET', 'accounts');
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Batch create leads.
|
|
*
|
|
* @param array $data
|
|
* @param string $object
|
|
* @param bool $isUpdate
|
|
*/
|
|
public function createLeads($data, $object = 'contacts', $isUpdate = false): array
|
|
{
|
|
if (0 === count($data)) {
|
|
return [];
|
|
}
|
|
|
|
$returnIds = [];
|
|
|
|
$batchId = substr(str_shuffle(uniqid('b', false)), 0, 6);
|
|
$changeId = substr(str_shuffle(uniqid('c', false)), 0, 6);
|
|
|
|
$settings['headers']['Content-Type'] = 'multipart/mixed;boundary=batch_'.$batchId;
|
|
$settings['headers']['Accept'] = 'application/json';
|
|
|
|
$odata = '--batch_'.$batchId.PHP_EOL;
|
|
$odata .= 'Content-Type: multipart/mixed;boundary=changeset_'.$changeId.PHP_EOL.PHP_EOL;
|
|
|
|
$contentId = 0;
|
|
foreach ($data as $objectId => $lead) {
|
|
++$contentId;
|
|
$odata .= '--changeset_'.$changeId.PHP_EOL;
|
|
$odata .= 'Content-Type: application/http'.PHP_EOL;
|
|
$odata .= 'Content-Transfer-Encoding:binary'.PHP_EOL;
|
|
$odata .= 'Content-ID: '.$objectId.PHP_EOL.PHP_EOL;
|
|
// $odata .= 'Content-ID: '.(++$contentId).PHP_EOL.PHP_EOL;
|
|
$returnIds[$objectId] = $contentId;
|
|
if (!$isUpdate) {
|
|
$oid = $objectId;
|
|
$objectId = sprintf('%04X%04X-%04X-%04X-%04X-%04X%04X%04X', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(32768, 49151), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535));
|
|
$returnIds[$objectId] = $oid; // save lead Id
|
|
}
|
|
$operation = sprintf('%s(%s)', $object, $objectId);
|
|
$odata .= sprintf('PATCH %s/%s HTTP/1.1', $this->getUrl(), $operation).PHP_EOL;
|
|
if ($isUpdate) {
|
|
$odata .= 'If-Match: *'.PHP_EOL;
|
|
} else {
|
|
$odata .= 'If-None-Match: *'.PHP_EOL;
|
|
}
|
|
$odata .= 'Content-Type: application/json;type=entry'.PHP_EOL.PHP_EOL;
|
|
$odata .= json_encode($lead).PHP_EOL;
|
|
}
|
|
$odata .= '--changeset_'.$changeId.'--'.PHP_EOL.PHP_EOL;
|
|
|
|
$odata .= '--batch_'.$batchId.'--'.PHP_EOL;
|
|
|
|
$settings['post_data'] = $odata;
|
|
$settings['curl_options'][CURLOPT_CRLF] = true;
|
|
|
|
$response = $this->request('$batch', [], 'POST', $object, $settings);
|
|
if ($isUpdate) {
|
|
return $returnIds;
|
|
}
|
|
|
|
return $this->parseRawHttpResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @param array $data
|
|
*/
|
|
public function updateLeads($data, $object = 'contacts'): array
|
|
{
|
|
return $this->createLeads($data, $object, true);
|
|
}
|
|
|
|
/**
|
|
* @see https://stackoverflow.com/questions/5483851/manually-parse-raw-http-data-with-php
|
|
*/
|
|
public function parseRawHttpResponse(ResponseInterface $response): array
|
|
{
|
|
$a_data = [];
|
|
$input = $response->getBody();
|
|
$contentType = $response->getHeaders()['Content-Type'];
|
|
// grab multipart boundary from content type header
|
|
preg_match('/boundary=(.*)$/', $contentType, $matches);
|
|
$boundary = $matches[1];
|
|
// split content by boundary and get rid of last -- element
|
|
$a_blocks = preg_split("/-+$boundary/", $input);
|
|
array_pop($a_blocks);
|
|
// there is only one batchresponse
|
|
$input = array_pop($a_blocks);
|
|
[$header, $input] = explode("\r\n\r\n", $input, 2);
|
|
foreach (explode("\r\n", $header) as $r) {
|
|
if (0 === stripos($r, 'Content-Type:')) {
|
|
[$headername, $contentType] = explode(':', $r, 2);
|
|
}
|
|
}
|
|
// grab multipart boundary from content type header
|
|
preg_match('/boundary=(.*)$/', $contentType, $matches);
|
|
$boundary = $matches[1];
|
|
// split content by boundary and get rid of last -- element
|
|
$a_blocks = preg_split("/-+$boundary/", $input);
|
|
array_pop($a_blocks);
|
|
// loop data blocks
|
|
foreach ($a_blocks as $block) {
|
|
if (empty($block)) {
|
|
continue;
|
|
}
|
|
if (false !== stripos($block, 'OData-EntityId:')) {
|
|
preg_match('/Content-ID: (\d+)/', $block, $matches);
|
|
$leadId = (count($matches) > 1) ? $matches[1] : 0;
|
|
// OData-EntityId: https://virlatinus.crm.dynamics.com/api/data/v8.2/contacts(2725f27c-2058-e711-8111-c4346bac1938)
|
|
preg_match('/OData-EntityId: .*\(([^\)]*)\)/', $block, $matches);
|
|
$oid = (count($matches) > 1) ? $matches[1] : '00000000-0000-0000-0000-000000000000';
|
|
$a_data[$oid] = $leadId;
|
|
}
|
|
}
|
|
|
|
return $a_data;
|
|
}
|
|
}
|