1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace OroCRM\Bundle\MagentoBundle\Provider; |
4
|
|
|
|
5
|
|
|
use Doctrine\Common\Persistence\ManagerRegistry; |
6
|
|
|
|
7
|
|
|
use Psr\Log\LoggerAwareInterface; |
8
|
|
|
|
9
|
|
|
use Oro\Bundle\ImportExportBundle\Context\ContextRegistry; |
10
|
|
|
use Oro\Bundle\ImportExportBundle\Context\ContextInterface; |
11
|
|
|
use Oro\Bundle\IntegrationBundle\Entity\Channel as Integration; |
12
|
|
|
use Oro\Bundle\IntegrationBundle\Entity\Status; |
13
|
|
|
use Oro\Bundle\IntegrationBundle\Exception\RuntimeException; |
14
|
|
|
use Oro\Bundle\IntegrationBundle\Logger\LoggerStrategy; |
15
|
|
|
use Oro\Bundle\IntegrationBundle\Provider\AbstractConnector; |
16
|
|
|
use Oro\Bundle\IntegrationBundle\Provider\ConnectorContextMediator; |
17
|
|
|
|
18
|
|
|
use OroCRM\Bundle\MagentoBundle\Provider\Iterator\PredefinedFiltersAwareInterface; |
19
|
|
|
use OroCRM\Bundle\MagentoBundle\Provider\Iterator\UpdatedLoaderInterface; |
20
|
|
|
use OroCRM\Bundle\MagentoBundle\Provider\Transport\MagentoTransportInterface; |
21
|
|
|
|
22
|
|
|
abstract class AbstractMagentoConnector extends AbstractConnector implements MagentoConnectorInterface |
23
|
|
|
{ |
24
|
|
|
const LAST_SYNC_KEY = 'lastSyncItemDate'; |
25
|
|
|
|
26
|
|
|
/** @var MagentoTransportInterface */ |
27
|
|
|
protected $transport; |
28
|
|
|
|
29
|
|
|
/** @var array */ |
30
|
|
|
protected $bundleConfiguration; |
31
|
|
|
|
32
|
|
|
/** @var ManagerRegistry */ |
33
|
|
|
protected $managerRegistry; |
34
|
|
|
|
35
|
|
|
/** @var string */ |
36
|
|
|
protected $className; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @param ContextRegistry $contextRegistry |
40
|
|
|
* @param LoggerStrategy $logger |
41
|
|
|
* @param ConnectorContextMediator $contextMediator |
42
|
|
|
* @param array $bundleConfiguration |
43
|
|
|
*/ |
44
|
|
|
public function __construct( |
45
|
|
|
ContextRegistry $contextRegistry, |
46
|
|
|
LoggerStrategy $logger, |
47
|
|
|
ConnectorContextMediator $contextMediator, |
48
|
|
|
array $bundleConfiguration |
49
|
|
|
) { |
50
|
|
|
parent::__construct($contextRegistry, $logger, $contextMediator); |
51
|
|
|
$this->bundleConfiguration = $bundleConfiguration; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @param ManagerRegistry $managerRegistry |
56
|
|
|
*/ |
57
|
|
|
public function setManagerRegistry(ManagerRegistry $managerRegistry) |
58
|
|
|
{ |
59
|
|
|
$this->managerRegistry = $managerRegistry; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @param string $className |
64
|
|
|
*/ |
65
|
|
|
public function setClassName($className) |
66
|
|
|
{ |
67
|
|
|
$this->className = $className; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* {@inheritdoc} |
72
|
|
|
*/ |
73
|
|
|
public function getImportEntityFQCN() |
74
|
|
|
{ |
75
|
|
|
if (!$this->className) { |
76
|
|
|
throw new \InvalidArgumentException(sprintf('Entity FQCN is missing for "%s" connector', $this->getType())); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
return $this->className; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* {@inheritdoc} |
84
|
|
|
*/ |
85
|
|
|
public function read() |
86
|
|
|
{ |
87
|
|
|
$item = parent::read(); |
88
|
|
|
|
89
|
|
|
if (null !== $item) { |
90
|
|
|
$this->addStatusData( |
91
|
|
|
self::LAST_SYNC_KEY, |
92
|
|
|
$this->getMaxUpdatedDate($this->getUpdatedDate($item), $this->getStatusData(self::LAST_SYNC_KEY)) |
93
|
|
|
); |
94
|
|
|
} |
95
|
|
|
$iterator = $this->getSourceIterator(); |
96
|
|
|
if (!$iterator->valid() && $iterator instanceof UpdatedLoaderInterface) { |
97
|
|
|
// cover case, when no one item was synced |
98
|
|
|
// then just take point from what it was started |
99
|
|
|
$dateFromReadStarted = $iterator->getStartDate() ? $iterator->getStartDate()->format('Y-m-d H:i:s') : null; |
100
|
|
|
$this->addStatusData( |
101
|
|
|
self::LAST_SYNC_KEY, |
102
|
|
|
$this->getMaxUpdatedDate($this->getStatusData(self::LAST_SYNC_KEY), $dateFromReadStarted) |
103
|
|
|
); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
return $item; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* @param ContextInterface $context |
111
|
|
|
*/ |
112
|
|
|
protected function initializeTransport(ContextInterface $context) |
113
|
|
|
{ |
114
|
|
|
$this->channel = $this->contextMediator->getChannel($context); |
115
|
|
|
$this->transport = $this->contextMediator->getInitializedTransport($this->channel, true); |
116
|
|
|
|
117
|
|
|
$this->validateConfiguration(); |
118
|
|
|
$this->setSourceIterator($this->getConnectorSource()); |
119
|
|
|
|
120
|
|
|
$sourceIterator = $this->getSourceIterator(); |
121
|
|
|
if ($sourceIterator instanceof LoggerAwareInterface) { |
122
|
|
|
$sourceIterator->setLogger($this->logger); |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* {@inheritdoc} |
128
|
|
|
*/ |
129
|
|
|
protected function initializeFromContext(ContextInterface $context) |
130
|
|
|
{ |
131
|
|
|
$this->initializeTransport($context); |
132
|
|
|
|
133
|
|
|
// set start date and mode depending on status |
134
|
|
|
/** @var Status $status */ |
135
|
|
|
$status = $this->getLastCompletedIntegrationStatus($this->channel, $this->getType()); |
136
|
|
|
$iterator = $this->getSourceIterator(); |
137
|
|
|
|
138
|
|
|
if ($iterator instanceof UpdatedLoaderInterface) { |
139
|
|
|
$startDate = $this->getStartDate($status); |
140
|
|
|
|
141
|
|
|
if ($status) { |
142
|
|
|
// use assumption interval in order to prevent mistiming issues |
143
|
|
|
$intervalString = $this->bundleConfiguration['sync_settings']['mistiming_assumption_interval']; |
144
|
|
|
$this->logger->debug(sprintf('Real start date: "%s"', $startDate->format(\DateTime::RSS))); |
145
|
|
|
$this->logger->debug(sprintf('Subtracted the presumable mistiming interval "%s"', $intervalString)); |
146
|
|
|
$startDate->sub(\DateInterval::createFromDateString($intervalString)); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
$executionContext = $this->stepExecution->getJobExecution()->getExecutionContext(); |
150
|
|
|
$interval = $executionContext->get(InitialSyncProcessor::INTERVAL); |
151
|
|
|
if ($interval) { |
152
|
|
|
$iterator->setMode(UpdatedLoaderInterface::IMPORT_MODE_INITIAL); |
153
|
|
|
$iterator->setSyncRange($interval); |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
$minimalSyncDate = $executionContext->get(InitialSyncProcessor::START_SYNC_DATE); |
157
|
|
|
if ($minimalSyncDate) { |
158
|
|
|
$iterator->setMinSyncDate($minimalSyncDate); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
$iterator->setStartDate($startDate); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
// pass filters from connector |
165
|
|
|
$this->setPredefinedFilters($context, $iterator); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* @param Status $status |
170
|
|
|
* |
171
|
|
|
* @return \DateTime |
172
|
|
|
*/ |
173
|
|
|
protected function getStartDate(Status $status = null) |
174
|
|
|
{ |
175
|
|
|
$jobContext = $this->stepExecution->getJobExecution()->getExecutionContext(); |
176
|
|
|
$initialSyncedTo = $jobContext->get(InitialSyncProcessor::INITIAL_SYNCED_TO); |
177
|
|
|
if ($initialSyncedTo) { |
178
|
|
|
return $initialSyncedTo; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
// If connector has status use it's information for start date |
182
|
|
|
if ($status) { |
183
|
|
|
$data = $status->getData(); |
184
|
|
|
if (empty($data[AbstractInitialProcessor::SKIP_STATUS])) { |
185
|
|
View Code Duplication |
if (!empty($data[self::LAST_SYNC_KEY])) { |
|
|
|
|
186
|
|
|
return new \DateTime($data[self::LAST_SYNC_KEY], new \DateTimeZone('UTC')); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
return clone $status->getDate(); |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
// If there is no status and LAST_SYNC_KEY is present in contexts use it |
194
|
|
|
$lastSyncDate = $this->stepExecution->getExecutionContext()->get(self::LAST_SYNC_KEY); |
195
|
|
|
if ($lastSyncDate) { |
196
|
|
|
return $lastSyncDate; |
197
|
|
|
} elseif ($jobContext->get(self::LAST_SYNC_KEY)) { |
198
|
|
|
return $jobContext->get(self::LAST_SYNC_KEY); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
return new \DateTime('now', new \DateTimeZone('UTC')); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Get last completed status for connector of integration instance. |
206
|
|
|
* |
207
|
|
|
* @param Integration $integration |
208
|
|
|
* @param string $connector |
209
|
|
|
* @return Status|null |
210
|
|
|
*/ |
211
|
|
|
protected function getLastCompletedIntegrationStatus(Integration $integration, $connector) |
212
|
|
|
{ |
213
|
|
|
if (!$this->managerRegistry) { |
214
|
|
|
throw new RuntimeException('Doctrine manager registry is not initialized. Use setManagerRegistry method.'); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
return $this->managerRegistry->getRepository('OroIntegrationBundle:Channel') |
218
|
|
|
->getLastStatusForConnector($integration, $connector, Status::STATUS_COMPLETED); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* {@inheritdoc} |
223
|
|
|
*/ |
224
|
|
|
protected function validateConfiguration() |
225
|
|
|
{ |
226
|
|
|
parent::validateConfiguration(); |
227
|
|
|
|
228
|
|
|
if (!$this->transport instanceof MagentoTransportInterface) { |
229
|
|
|
throw new \LogicException('Option "transport" should implement "MagentoTransportInterface"'); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* @param string|null $currDateToCompare |
235
|
|
|
* @param string|null $prevDateToCompare |
236
|
|
|
* |
237
|
|
|
* @return null|string |
238
|
|
|
*/ |
239
|
|
|
protected function getMaxUpdatedDate($currDateToCompare, $prevDateToCompare) |
240
|
|
|
{ |
241
|
|
|
if (!$prevDateToCompare) { |
|
|
|
|
242
|
|
|
$date = $currDateToCompare; |
243
|
|
|
} elseif (!$currDateToCompare) { |
|
|
|
|
244
|
|
|
$date = $prevDateToCompare; |
245
|
|
|
} else { |
246
|
|
|
$date = strtotime($currDateToCompare) > strtotime($prevDateToCompare) |
247
|
|
|
? $currDateToCompare |
248
|
|
|
: $prevDateToCompare; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
return $date ? $this->getMinUpdatedDate($date) : $date; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Compares maximum updated date with current date and returns the smallest. |
256
|
|
|
* |
257
|
|
|
* @param string $updatedDate |
258
|
|
|
* |
259
|
|
|
* @return string |
260
|
|
|
*/ |
261
|
|
|
protected function getMinUpdatedDate($updatedDate) |
262
|
|
|
{ |
263
|
|
|
$currentDate = new \DateTime('now', new \DateTimeZone('UTC')); |
264
|
|
|
if ($currentDate->getTimestamp() > strtotime($updatedDate)) { |
265
|
|
|
return $updatedDate; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
return $currentDate->format('Y-m-d H:i:s'); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* @param array $item |
273
|
|
|
* |
274
|
|
|
* @return string|null |
275
|
|
|
*/ |
276
|
|
|
protected function getUpdatedDate(array $item) |
277
|
|
|
{ |
278
|
|
|
switch (true) { |
279
|
|
|
case !empty($item['updatedAt']): |
280
|
|
|
return $item['updatedAt']; |
281
|
|
|
break; |
|
|
|
|
282
|
|
|
case !empty($item['updated_at']): |
283
|
|
|
return $item['updated_at']; |
284
|
|
|
break; |
|
|
|
|
285
|
|
|
default: |
286
|
|
|
return null; |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* @param ContextInterface $context |
292
|
|
|
* @param \Iterator $iterator |
293
|
|
|
*/ |
294
|
|
|
protected function setPredefinedFilters(ContextInterface $context, \Iterator $iterator) |
295
|
|
|
{ |
296
|
|
|
$filters = null; |
297
|
|
|
if ($context->hasOption('filters')) { |
298
|
|
|
$filters = $context->getOption('filters'); |
299
|
|
|
$context->removeOption('filters'); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
$complexFilters = null; |
303
|
|
|
if ($context->hasOption('complex_filters')) { |
304
|
|
|
$complexFilters = $context->getOption('complex_filters'); |
305
|
|
|
$context->removeOption('complex_filters'); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
if ($filters || $complexFilters) { |
309
|
|
|
if ($iterator instanceof PredefinedFiltersAwareInterface) { |
310
|
|
|
$predefinedFilters = new BatchFilterBag((array)$filters, (array)$complexFilters); |
311
|
|
|
|
312
|
|
|
$iterator->setPredefinedFiltersBag($predefinedFilters); |
313
|
|
|
} else { |
314
|
|
|
throw new \LogicException('Iterator does not support predefined filters'); |
315
|
|
|
} |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.