Issues (3627)

plugins/MauticCrmBundle/Api/DynamicsApi.php (4 issues)

1
<?php
2
3
namespace MauticPlugin\MauticCrmBundle\Api;
4
5
use Joomla\Http\Response;
6
use Mautic\LeadBundle\Entity\Lead;
7
use Mautic\PluginBundle\Exception\ApiErrorException;
8
9
class DynamicsApi extends CrmApi
10
{
11
    /**
12
     * @return string
13
     */
14
    private function getUrl()
15
    {
16
        $keys = $this->integration->getKeys();
17
18
        return $keys['resource'].'/api/data/v8.2';
19
    }
20
21
    /**
22
     * @param $operation
23
     * @param string $method
24
     * @param string $moduleobject
25
     *
26
     * @return mixed|string
27
     *
28
     * @throws ApiErrorException
29
     */
30
    protected function request($operation, array $parameters = [], $method = 'GET', $moduleobject = 'contacts', $settings = [])
31
    {
32
        if ('company' === $moduleobject) {
33
            $moduleobject = 'accounts';
34
        }
35
36
        if ('' === $operation) {
37
            $operation = $moduleobject;
38
        }
39
40
        $url = sprintf('%s/%s', $this->getUrl(), $operation);
41
42
        if (isset($parameters['request_settings'])) {
43
            $settings = array_merge($settings, $parameters['request_settings']);
44
            unset($parameters['request_settings']);
45
        }
46
47
        $settings = array_merge($settings, [
48
            'encode_parameters' => 'json',
49
            'return_raw'        => 'true', // needed to get the HTTP status code in the response
50
            'request_timeout'   => 300,
51
        ]);
52
53
        /** @var Response $response */
54
        $response = $this->integration->makeRequest($url, $parameters, $method, $settings);
55
56
        if ('POST' === $method && (!is_object($response) || !in_array($response->code, [200, 204], true))) {
57
            throw new ApiErrorException('Dynamics CRM API error: '.json_encode($response));
58
        }
59
60
        if ('GET' === $method && is_object($response) && property_exists($response, 'body')) {
61
            return json_decode($response->body, true);
62
        }
63
64
        return $response;
65
    }
66
67
    /**
68
     * List types.
69
     *
70
     * @param string $object Zoho module name
71
     *
72
     * @return mixed
73
     */
74
    public function getLeadFields($object = 'contacts')
75
    {
76
        if ('company' === $object) {
77
            $object = 'accounts'; // Dynamics object name
78
        }
79
80
        $logicalName = rtrim($object, 's'); // singularize object name
81
82
        $operation  = sprintf('EntityDefinitions(LogicalName=\'%s\')/Attributes', $logicalName);
83
        $parameters = [
84
            '$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
85
            '$select' => 'RequiredLevel,LogicalName,DisplayName,AttributeTypeName', // select only miningful columns
86
        ];
87
88
        return $this->request($operation, $parameters, 'GET', $object);
89
    }
90
91
    /**
92
     * @param $data
93
     * @param Lead $lead
94
     * @param $object
95
     *
96
     * @return Response
97
     */
98
    public function createLead($data, $lead, $object = 'contacts')
99
    {
100
        return $this->request('', $data, 'POST', $object);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->request('', $data, 'POST', $object) also could return the type string which is incompatible with the documented return type Joomla\Http\Response.
Loading history...
101
    }
102
103
    /**
104
     * @param $data
105
     * @param $objectId
106
     *
107
     * @return Response
108
     */
109
    public function updateLead($data, $objectId)
110
    {
111
        //        $settings['headers']['If-Match'] = '*'; // prevent create new contact
112
        return $this->request(sprintf('contacts(%s)', $objectId), $data, 'PATCH', 'contacts', []);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->request(sp...', 'contacts', array()) also could return the type string which is incompatible with the documented return type Joomla\Http\Response.
Loading history...
113
    }
114
115
    /**
116
     * gets leads.
117
     *
118
     * @return mixed
119
     */
120
    public function getLeads(array $params)
121
    {
122
        return $this->request('', $params, 'GET', 'contacts');
123
    }
124
125
    /**
126
     * gets companies.
127
     *
128
     * @param string $id
129
     *
130
     * @return mixed
131
     */
132
    public function getCompanies(array $params, $id = null)
133
    {
134
        if ($id) {
135
            $operation = sprintf('accounts(%s)', $id);
136
            $data      = $this->request($operation, $params, 'GET');
137
        } else {
138
            $data = $this->request('', $params, 'GET', 'accounts');
139
        }
140
141
        return $data;
142
    }
143
144
    /**
145
     * Batch create leads.
146
     *
147
     * @param array  $data
148
     * @param string $object
149
     * @param bool   $isUpdate
150
     *
151
     * @return array
152
     */
153
    public function createLeads($data, $object = 'contacts', $isUpdate = false)
154
    {
155
        if (0 === count($data)) {
156
            return [];
157
        }
158
159
        $returnIds = [];
160
161
        $batchId  = substr(str_shuffle(uniqid('b', false)), 0, 6);
162
        $changeId = substr(str_shuffle(uniqid('c', false)), 0, 6);
163
164
        $settings['headers']['Content-Type'] = 'multipart/mixed;boundary=batch_'.$batchId;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$settings was never initialized. Although not strictly required by PHP, it is generally a good practice to add $settings = array(); before regardless.
Loading history...
165
        $settings['headers']['Accept']       = 'application/json';
166
167
        $odata = '--batch_'.$batchId.PHP_EOL;
168
        $odata .= 'Content-Type: multipart/mixed;boundary=changeset_'.$changeId.PHP_EOL.PHP_EOL;
169
170
        $contentId = 0;
171
        foreach ($data as $objectId => $lead) {
172
            ++$contentId;
173
            $odata .= '--changeset_'.$changeId.PHP_EOL;
174
            $odata .= 'Content-Type: application/http'.PHP_EOL;
175
            $odata .= 'Content-Transfer-Encoding:binary'.PHP_EOL;
176
            $odata .= 'Content-ID: '.$objectId.PHP_EOL.PHP_EOL;
177
//            $odata .= 'Content-ID: '.(++$contentId).PHP_EOL.PHP_EOL;
178
            $returnIds[$objectId] = $contentId;
179
            if (!$isUpdate) {
180
                $oid                  = $objectId;
181
                $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));
182
                $returnIds[$objectId] = $oid; // save lead Id
183
            }
184
            $operation = sprintf('%s(%s)', $object, $objectId);
185
            $odata .= sprintf('PATCH %s/%s HTTP/1.1', $this->getUrl(), $operation).PHP_EOL;
186
            if ($isUpdate) {
187
                $odata .= 'If-Match: *'.PHP_EOL;
188
            } else {
189
                $odata .= 'If-None-Match: *'.PHP_EOL;
190
            }
191
            $odata .= 'Content-Type: application/json;type=entry'.PHP_EOL.PHP_EOL;
192
            $odata .= json_encode($lead).PHP_EOL;
193
        }
194
        $odata .= '--changeset_'.$changeId.'--'.PHP_EOL.PHP_EOL;
195
196
        $odata .= '--batch_'.$batchId.'--'.PHP_EOL;
197
198
        $settings['post_data']                  = $odata;
199
        $settings['curl_options'][CURLOPT_CRLF] = true;
200
201
        $response = $this->request('$batch', [], 'POST', $object, $settings);
202
        if ($isUpdate) {
203
            return $returnIds;
204
        }
205
206
        return $this->parseRawHttpResponse($response);
0 ignored issues
show
It seems like $response can also be of type string; however, parameter $response of MauticPlugin\MauticCrmBu...:parseRawHttpResponse() does only seem to accept Joomla\Http\Response, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

206
        return $this->parseRawHttpResponse(/** @scrutinizer ignore-type */ $response);
Loading history...
207
    }
208
209
    /**
210
     * @param array $data
211
     * @param $object
212
     *
213
     * @return array
214
     */
215
    public function updateLeads($data, $object = 'contacts')
216
    {
217
        return $this->createLeads($data, $object, true);
218
    }
219
220
    /**
221
     * @see https://stackoverflow.com/questions/5483851/manually-parse-raw-http-data-with-php
222
     *
223
     * @return array
224
     */
225
    public function parseRawHttpResponse(Response $response)
226
    {
227
        $a_data      = [];
228
        $input       = $response->body;
229
        $contentType = $response->headers['Content-Type'];
230
        // grab multipart boundary from content type header
231
        preg_match('/boundary=(.*)$/', $contentType, $matches);
232
        $boundary = $matches[1];
233
        // split content by boundary and get rid of last -- element
234
        $a_blocks = preg_split("/-+$boundary/", $input);
235
        array_pop($a_blocks);
236
        // there is only one batchresponse
237
        $input                = array_pop($a_blocks);
238
        list($header, $input) = explode("\r\n\r\n", $input, 2);
239
        foreach (explode("\r\n", $header) as $r) {
240
            if (0 === stripos($r, 'Content-Type:')) {
241
                list($headername, $contentType) = explode(':', $r, 2);
242
            }
243
        }
244
        // grab multipart boundary from content type header
245
        preg_match('/boundary=(.*)$/', $contentType, $matches);
246
        $boundary = $matches[1];
247
        // split content by boundary and get rid of last -- element
248
        $a_blocks = preg_split("/-+$boundary/", $input);
249
        array_pop($a_blocks);
250
        // loop data blocks
251
        foreach ($a_blocks as $block) {
252
            if (empty($block)) {
253
                continue;
254
            }
255
            if (false !== stripos($block, 'OData-EntityId:')) {
256
                preg_match('/Content-ID: (\d+)/', $block, $matches);
257
                $leadId = (count($matches) > 1) ? $matches[1] : 0;
258
                // OData-EntityId: https://virlatinus.crm.dynamics.com/api/data/v8.2/contacts(2725f27c-2058-e711-8111-c4346bac1938)
259
                preg_match('/OData-EntityId: .*\(([^\)]*)\)/', $block, $matches);
260
                $oid          = (count($matches) > 1) ? $matches[1] : '00000000-0000-0000-0000-000000000000';
261
                $a_data[$oid] = $leadId;
262
            }
263
        }
264
265
        return $a_data;
266
    }
267
}
268