Skip to content

Commit 9d5a472

Browse files
authored
Merge pull request #78 from TheDMSGroup/ENG-312-contextual-token-bug
[ENG-312] Contextual token bug
2 parents 899ae1d + f3a479a commit 9d5a472

File tree

10 files changed

+157
-66
lines changed

10 files changed

+157
-66
lines changed

Command/SendContactCommand.php

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,19 @@ protected function execute(InputInterface $input, OutputInterface $output)
8888
/** @var ClientModel $clientModel */
8989
$clientModel = $container->get('mautic.contactclient.model.contactclient');
9090
/** @var ContactClient $client */
91-
$client = $clientModel->getEntity($options['client']);
91+
$client = $clientModel->getEntity($options['client']);
9292
if (!$client) {
93-
$output->writeln('<error>'.$translator->trans('mautic.contactclient.sendcontact.error.client.load').'</error>');
93+
$output->writeln(
94+
'<error>'.$translator->trans('mautic.contactclient.sendcontact.error.client.load').'</error>'
95+
);
9496

9597
return 1;
9698
}
9799

98100
if (false === $client->getIsPublished() && !$options['force']) {
99-
$output->writeln('<error>'.$translator->trans('mautic.contactclient.sendcontact.error.client.publish').' .</error>');
101+
$output->writeln(
102+
'<error>'.$translator->trans('mautic.contactclient.sendcontact.error.client.publish').' .</error>'
103+
);
100104

101105
return 1;
102106
}
@@ -106,7 +110,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
106110
/** @var \Mautic\LeadBundle\Entity\Lead $contact */
107111
$contact = $contactModel->getEntity($options['contact']);
108112
if (!$contact) {
109-
$output->writeln('<error>'.$translator->trans('mautic.contactclient.sendcontact.error.contact.load').'</error>');
113+
$output->writeln(
114+
'<error>'.$translator->trans('mautic.contactclient.sendcontact.error.contact.load').'</error>'
115+
);
110116

111117
return 1;
112118
}
@@ -121,18 +127,24 @@ protected function execute(InputInterface $input, OutputInterface $output)
121127
!$integrationObject
122128
|| (false === $integrationObject->getIntegrationSettings()->getIsPublished() && !$options['force'])
123129
) {
124-
$output->writeln('<error>'.$translator->trans('mautic.contactclient.sendcontact.error.plugin.publish').'</error>');
130+
$output->writeln(
131+
'<error>'.$translator->trans('mautic.contactclient.sendcontact.error.plugin.publish').'</error>'
132+
);
125133

126134
return 1;
127135
}
128136
$integrationObject->sendContact($client, $contact, $options['test']);
129137
if ($integrationObject->getValid()) {
130-
$output->writeln('<info>'.$translator->trans('mautic.contactclient.sendcontact.contact.accepted').'</info>');
138+
$output->writeln(
139+
'<info>'.$translator->trans('mautic.contactclient.sendcontact.contact.accepted').'</info>'
140+
);
131141
if (isset($options['verbose']) && $options['verbose']) {
132142
$output->writeln('<info>'.$integrationObject->getLogsYAML().'</info>');
133143
}
134144
} else {
135-
$output->writeln('<error>'.$translator->trans('mautic.contactclient.sendcontact.contact.rejected').'</error>');
145+
$output->writeln(
146+
'<error>'.$translator->trans('mautic.contactclient.sendcontact.contact.rejected').'</error>'
147+
);
136148
if (isset($options['verbose']) && $options['verbose']) {
137149
$output->writeln('<info>'.$integrationObject->getLogsYAML().'</info>');
138150
}

Config/config.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
'path' => '/contactclient/timeline/export/{contactClientId}',
3838
'controller' => 'MauticContactClientBundle:Timeline:exportTimeline',
3939
],
40-
'mautic_contactclient_timeline_file' => [
40+
'mautic_contactclient_timeline_file' => [
4141
'path' => '/contactclient/timeline/file/{contactClientId}/{fileId}',
4242
'controller' => 'MauticContactClientBundle:Timeline:file',
4343
],

Controller/TimelineController.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use Mautic\CoreBundle\Controller\CommonController;
1515
use Mautic\CoreBundle\Helper\InputHelper;
16-
use MauticPlugin\MauticContactClientBundle\Entity\File;
1716
use Symfony\Component\HttpFoundation\BinaryFileResponse;
1817
use Symfony\Component\HttpFoundation\Request;
1918
use Symfony\Component\HttpFoundation\Response;

Helper/TokenHelper.php

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ public function addContextContact(Contact $contact = null)
215215
}
216216
}
217217

218+
// @todo - Refactor to only get field values that we may need based on all tokens in use throughout operations.
218219
$fieldGroups = $contact->getFields();
219220
if ($fieldGroups) {
220221
foreach ($fieldGroups as $fgKey => $fieldGroup) {
@@ -235,29 +236,67 @@ public function addContextContact(Contact $contact = null)
235236
}
236237
}
237238

238-
$contacts = !empty($this->context['contacts']) ? $this->context['contacts'] : [];
239+
// Support multiple contacts in the future if needed by uncommenting a bit here.
240+
// $contacts = !empty($this->context['contacts']) ? $this->context['contacts'] : [];
239241

240242
// Set the context to this contact.
241243
$this->context = $context;
242244

243245
// Support multiple contacts for future batch processing.
244-
$this->context['contacts'] = $contacts;
245-
$this->context['contacts'][$context['id']] = $context;
246+
// $this->context['contacts'] = $contacts;
247+
// $this->context['contacts'][$context['id']] = $context;
246248

247249
return $this;
248250
}
249251

250252
/**
253+
* Simplify the payload for tokens, including actual response data when possible.
254+
*
251255
* @param array $payload
256+
* @param null $operationId
257+
* @param array $responseActual
252258
*
253259
* @return $this
254260
*/
255-
public function addContextPayload($payload = [])
261+
public function addContextPayload($payload = [], $operationId = null, $responseActual = [])
256262
{
257263
if (!$payload) {
258264
return $this;
259265
}
260-
$this->addContext(['payload' => $payload]);
266+
$payload = json_decode(json_encode($payload), true);
267+
if (!isset($this->context['payload'])) {
268+
$this->context['payload'] = $payload;
269+
}
270+
if (!empty($payload['operations'])) {
271+
foreach ($payload['operations'] as $id => $operation) {
272+
foreach (['request', 'response'] as $opType) {
273+
if (!empty($operation[$opType])) {
274+
foreach (['headers', 'body'] as $fieldType) {
275+
if (!empty($operation[$opType][$fieldType])) {
276+
$fieldSet = [];
277+
if ('request' === $opType) {
278+
foreach ($operation[$opType][$fieldType] as $field) {
279+
if (!empty($field['key'])) {
280+
if (!empty($field['value'])) {
281+
$fieldSet[$field['key']] = $field['value'];
282+
}
283+
}
284+
}
285+
} elseif ('response' === $opType) {
286+
if (
287+
$id === $operationId
288+
&& !empty($responseActual[$fieldType])
289+
) {
290+
$fieldSet = $responseActual[$fieldType];
291+
}
292+
}
293+
$this->context['payload']['operations'][$id][$opType][$fieldType] = $fieldSet;
294+
}
295+
}
296+
}
297+
}
298+
}
299+
}
261300
}
262301

263302
/**
@@ -360,22 +399,25 @@ public function render($string, $force = false)
360399

361400
/**
362401
* @param bool $labeled
402+
* @param bool $flattened
363403
*
364404
* @return array
365405
*/
366-
public function getContext($labeled = false)
406+
public function getContext($labeled = false, $flattened = false)
367407
{
408+
$result = [];
368409
if ($labeled) {
369-
// When retrieving labels, nested contacts are not needed.
370-
unset($this->context['contacts']);
371-
$labels = $this->labels($this->context);
372-
$flatLabels = [];
373-
$this->flattenArray($labels, $flatLabels);
374-
375-
return $flatLabels;
410+
$labels = $this->labels($this->context);
411+
$this->flattenArray($labels, $result);
376412
} else {
377-
return $this->context;
413+
if ($flattened) {
414+
$this->flattenArray($this->context, $result);
415+
} else {
416+
$result = $this->context;
417+
}
378418
}
419+
420+
return $result;
379421
}
380422

381423
/**

Integration/ClientIntegration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ public function getLogsJSON()
805805
{
806806
return json_encode(
807807
$this->getLogs(),
808-
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_PRETTY_PRINT
808+
JSON_PRETTY_PRINT
809809
);
810810
}
811811

Model/ApiPayload.php

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class ApiPayload
100100
/** @var Schedule */
101101
protected $scheduleModel;
102102

103+
/** @var bool */
104+
protected $updatedFields = false;
105+
103106
/**
104107
* ApiPayload constructor.
105108
*
@@ -329,6 +332,7 @@ public function run()
329332
$transport = $this->getTransport();
330333
$tokenHelper = $this->tokenHelper->newSession($this->contactClient, $this->contact, $this->payload);
331334
$updatePayload = (bool) $this->settings['autoUpdate'];
335+
$opsRemaining = count($this->payload->operations);
332336

333337
foreach ($this->payload->operations as $id => &$operation) {
334338
$logs = [];
@@ -340,7 +344,7 @@ public function run()
340344
$apiOperation->run();
341345
$this->valid = $apiOperation->getValid();
342346
} catch (\Exception $e) {
343-
// Delay this exception throw...
347+
// Delay this exception throw till after we can do some important logging.
344348
}
345349
$logs = array_merge($apiOperation->getLogs(), $logs);
346350
$this->setLogs($logs, $id);
@@ -351,7 +355,17 @@ public function run()
351355
} else {
352356
// Aggregate successful responses that are mapped to Contact fields.
353357
$this->responseMap = array_merge($this->responseMap, $apiOperation->getResponseMap());
354-
$this->setAggregateActualResponses($apiOperation->getResponseActual());
358+
$responseActual = $apiOperation->getResponseActual();
359+
$this->setAggregateActualResponses($responseActual);
360+
--$opsRemaining;
361+
if ($opsRemaining) {
362+
// Update the contextual awareness for subsequent requests if needed.
363+
$this->applyResponseMap(true);
364+
// Update context to include actual previous payload responses.
365+
if ($responseActual) {
366+
$this->tokenHelper->addContextPayload($this->payload, $id, $responseActual);
367+
}
368+
}
355369
}
356370
}
357371

@@ -361,7 +375,7 @@ public function run()
361375
}
362376

363377
// Update the payload if enabled.
364-
if ($updatePayload) {
378+
if ($updatePayload && $this->test) {
365379
$this->updatePayload();
366380
}
367381

@@ -409,6 +423,44 @@ public function setAggregateActualResponses($responseActual, $types = ['headers'
409423
return $this;
410424
}
411425

426+
/**
427+
* Apply the responsemap to update a contact entity.
428+
*
429+
* @param bool $updateTokens
430+
*
431+
* @return bool
432+
*/
433+
public function applyResponseMap($updateTokens = false)
434+
{
435+
$responseMap = $this->getResponseMap();
436+
// Check the responseMap to discern where field values should go.
437+
if (count($responseMap)) {
438+
foreach ($responseMap as $alias => $value) {
439+
$oldValue = $this->contact->getFieldValue($alias);
440+
if ($oldValue !== $value) {
441+
$this->contact->addUpdatedField($alias, $value, $oldValue);
442+
if ($updateTokens) {
443+
$this->tokenHelper->addContext([$alias => $value]);
444+
}
445+
$this->setLogs('Updating Contact: '.$alias.' = '.$value, 'fieldsUpdated');
446+
$this->updatedFields = true;
447+
}
448+
}
449+
}
450+
451+
return $this->updatedFields;
452+
}
453+
454+
/**
455+
* Return the aggregate responsemap of all valid operations.
456+
*
457+
* @return array
458+
*/
459+
public function getResponseMap()
460+
{
461+
return $this->responseMap;
462+
}
463+
412464
/**
413465
* Update the payload with the parent ContactClient because we've updated the response expectation.
414466
*/
@@ -492,40 +544,6 @@ public function setLogs($value, $type = null)
492544
}
493545
}
494546

495-
/**
496-
* Apply the responsemap to update a contact entity.
497-
*
498-
* @return bool
499-
*/
500-
public function applyResponseMap()
501-
{
502-
$updated = false;
503-
$responseMap = $this->getResponseMap();
504-
// Check the responseMap to discern where field values should go.
505-
if (count($responseMap)) {
506-
foreach ($responseMap as $alias => $value) {
507-
$oldValue = $this->contact->getFieldValue($alias);
508-
if ($oldValue !== $value) {
509-
$this->contact->addUpdatedField($alias, $value, $oldValue);
510-
$this->setLogs('Updating Contact: '.$alias.' = '.$value);
511-
$updated = true;
512-
}
513-
}
514-
}
515-
516-
return $updated;
517-
}
518-
519-
/**
520-
* Return the aggregate responsemap of all valid operations.
521-
*
522-
* @return array
523-
*/
524-
public function getResponseMap()
525-
{
526-
return $this->responseMap;
527-
}
528-
529547
/**
530548
* Retrieve from the payload all outgoing fields that are set to overridable.
531549
*

Model/ApiPayloadOperation.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public function updatePayloadResponse()
207207
$result->{$type}[$fieldId]->example = $value;
208208
$updates = true;
209209
$this->setLogs(
210-
'Existing '.$type.' field "'.$key.'" now has an example: '.$value,
210+
'Existing '.$type.' field '.$key.' now has an example: '.$value,
211211
'autoUpdate'
212212
);
213213
} else {
@@ -216,7 +216,7 @@ public function updatePayloadResponse()
216216
$result->{$type}[$fieldId]->example = $value;
217217
$updates = true;
218218
$this->setLogs(
219-
'Existing '.$type.' field "'.$key.'" has a new example: '.$value,
219+
'Existing '.$type.' field '.$key.' has a new example: '.$value,
220220
'autoUpdate'
221221
);
222222
}

Model/ApiPayloadRequest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ public function send()
232232
}
233233
}
234234
$this->setLogs(microtime(true) - $startTime, 'duration');
235+
if ($this->test) {
236+
$this->setLogs($this->tokenHelper->getContext(false, true), 'availableTokens');
237+
}
235238
}
236239

237240
/**

Model/ApiPayloadResponse.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,21 @@ private function getResponseArray($data, $responseExpectedFormat = 'json')
188188
$doc->recover = true;
189189
$data = trim($data);
190190
// Ensure UTF-8 encoding is handled correctly.
191-
if (1 !== preg_match('/<\??xml .*encoding=["|\']?UTF-8["|\']?.*>/iU', $data, $matches)) {
192-
$data = '<?xml version="1.0" encoding="UTF-8"?>'.$data;
191+
if (1 !== preg_match('/^<\??xml .*encoding=["|\']?UTF-8["|\']?.*>/iU', $data, $matches)) {
192+
// Possibly missing UTF-8 specification.
193+
$opening = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
194+
$replaceCount = 0;
195+
$data = preg_replace(
196+
'/^<\??xml(.*)\?*>/iU',
197+
$opening,
198+
$data,
199+
1,
200+
$replaceCount
201+
);
202+
if (!$replaceCount) {
203+
// Missing opening XML block entirely.
204+
$data = $opening.$data;
205+
}
193206
}
194207
if ('html' == $responseExpectedFormat) {
195208
@$doc->loadHTML($data);

0 commit comments

Comments
 (0)