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
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
|
|||||
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
|
|||||
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
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 |