1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Oro\Bundle\ActivityContactBundle\EventListener; |
4
|
|
|
|
5
|
|
|
use Doctrine\Common\Util\ClassUtils; |
6
|
|
|
use Doctrine\ORM\EntityManager; |
7
|
|
|
use Doctrine\ORM\Event\OnFlushEventArgs; |
8
|
|
|
use Doctrine\ORM\Event\PostFlushEventArgs; |
9
|
|
|
|
10
|
|
|
use Symfony\Component\PropertyAccess\PropertyAccess; |
11
|
|
|
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; |
12
|
|
|
|
13
|
|
|
use Oro\Bundle\ActivityBundle\Event\ActivityEvent; |
14
|
|
|
use Oro\Bundle\EntityBundle\ORM\DoctrineHelper; |
15
|
|
|
use Oro\Bundle\EntityConfigBundle\Config\ConfigManager; |
16
|
|
|
use Oro\Bundle\ActivityContactBundle\Model\TargetExcludeList; |
17
|
|
|
use Oro\Bundle\ActivityContactBundle\Direction\DirectionProviderInterface; |
18
|
|
|
use Oro\Bundle\ActivityContactBundle\EntityConfig\ActivityScope; |
19
|
|
|
use Oro\Bundle\ActivityContactBundle\Provider\ActivityContactProvider; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
23
|
|
|
*/ |
24
|
|
|
class ActivityListener |
25
|
|
|
{ |
26
|
|
|
/** @var ActivityContactProvider */ |
27
|
|
|
protected $activityContactProvider; |
28
|
|
|
|
29
|
|
|
/** @var DoctrineHelper */ |
30
|
|
|
protected $doctrineHelper; |
31
|
|
|
|
32
|
|
|
/** @var array */ |
33
|
|
|
protected $deletedEntities = []; |
34
|
|
|
|
35
|
|
|
/** @var array */ |
36
|
|
|
protected $updatedEntities = []; |
37
|
|
|
|
38
|
|
|
/** @var ConfigManager */ |
39
|
|
|
protected $configManager; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @param ActivityContactProvider $activityContactProvider |
43
|
|
|
* @param DoctrineHelper $doctrineHelper |
44
|
|
|
* @param ConfigManager $configManager |
45
|
|
|
*/ |
46
|
|
|
public function __construct( |
47
|
|
|
ActivityContactProvider $activityContactProvider, |
48
|
|
|
DoctrineHelper $doctrineHelper, |
49
|
|
|
ConfigManager $configManager |
50
|
|
|
) { |
51
|
|
|
$this->activityContactProvider = $activityContactProvider; |
52
|
|
|
$this->doctrineHelper = $doctrineHelper; |
53
|
|
|
$this->configManager = $configManager; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Recalculate activity contacts on add new activity to the target |
58
|
|
|
* |
59
|
|
|
* @param ActivityEvent $event |
60
|
|
|
*/ |
61
|
|
|
public function onAddActivity(ActivityEvent $event) |
62
|
|
|
{ |
63
|
|
|
$activity = $event->getActivity(); |
64
|
|
|
$target = $event->getTarget(); |
65
|
|
|
$extendProvider = $this->configManager->getProvider('extend'); |
66
|
|
|
$targetClassName = ClassUtils::getClass($target); |
67
|
|
|
|
68
|
|
|
if (TargetExcludeList::isExcluded($targetClassName) || |
69
|
|
|
!$extendProvider->getConfig($targetClassName)->is('is_extend')) { |
70
|
|
|
return; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
$direction = $this->activityContactProvider->getActivityDirection($activity, $target); |
74
|
|
|
if ($direction !== DirectionProviderInterface::DIRECTION_UNKNOWN) { |
75
|
|
|
$accessor = PropertyAccess::createPropertyAccessor(); |
76
|
|
|
|
77
|
|
|
$contactDate = $this->activityContactProvider->getActivityDate($activity); |
78
|
|
|
|
79
|
|
|
$accessor->setValue( |
80
|
|
|
$target, |
81
|
|
|
ActivityScope::CONTACT_COUNT, |
82
|
|
|
((int)$accessor->getValue($target, ActivityScope::CONTACT_COUNT) + 1) |
83
|
|
|
); |
84
|
|
|
|
85
|
|
|
$lastContactDate = $accessor->getValue($target, ActivityScope::LAST_CONTACT_DATE); |
86
|
|
|
if ($lastContactDate < $contactDate) { |
87
|
|
|
$accessor->setValue($target, ActivityScope::LAST_CONTACT_DATE, $contactDate); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
if ($direction === DirectionProviderInterface::DIRECTION_INCOMING) { |
91
|
|
|
$directionCountPath = ActivityScope::CONTACT_COUNT_IN; |
92
|
|
|
$contactDatePath = ActivityScope::LAST_CONTACT_DATE_IN; |
93
|
|
|
} else { |
94
|
|
|
$directionCountPath = ActivityScope::CONTACT_COUNT_OUT; |
95
|
|
|
$contactDatePath = ActivityScope::LAST_CONTACT_DATE_OUT; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
$accessor->setValue( |
99
|
|
|
$target, |
100
|
|
|
$directionCountPath, |
101
|
|
|
((int)$accessor->getValue($target, $directionCountPath) + 1) |
102
|
|
|
); |
103
|
|
|
|
104
|
|
|
$lastContactDate = $accessor->getValue($target, $contactDatePath); |
105
|
|
|
if ($lastContactDate < $contactDate) { |
106
|
|
|
$accessor->setValue($target, $contactDatePath, $contactDate); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* @param ActivityEvent $event |
113
|
|
|
*/ |
114
|
|
|
public function onRemoveActivity(ActivityEvent $event) |
115
|
|
|
{ |
116
|
|
|
$activity = $event->getActivity(); |
117
|
|
|
$target = $event->getTarget(); |
118
|
|
|
$extendProvider = $this->configManager->getProvider('extend'); |
119
|
|
|
$targetClassName = ClassUtils::getClass($target); |
120
|
|
|
|
121
|
|
|
if (TargetExcludeList::isExcluded($targetClassName) || |
122
|
|
|
!$extendProvider->getConfig($targetClassName)->is('is_extend')) { |
123
|
|
|
return; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
$accessor = PropertyAccess::createPropertyAccessor(); |
127
|
|
|
$accessor->setValue( |
128
|
|
|
$target, |
129
|
|
|
ActivityScope::CONTACT_COUNT, |
130
|
|
|
(int)$accessor->getValue($target, ActivityScope::CONTACT_COUNT) - 1 |
131
|
|
|
); |
132
|
|
|
|
133
|
|
|
$direction = $this->activityContactProvider->getActivityDirection($activity, $target); |
|
|
|
|
134
|
|
|
list($directionProperty) = $this->getDirectionProperties($direction); |
135
|
|
|
$accessor->setValue( |
136
|
|
|
$target, |
137
|
|
|
$directionProperty, |
138
|
|
|
(int)$accessor->getValue($target, $directionProperty) - 1 |
139
|
|
|
); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Collect activities changes |
144
|
|
|
* |
145
|
|
|
* @param OnFlushEventArgs $args |
146
|
|
|
* |
147
|
|
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) |
148
|
|
|
*/ |
149
|
|
|
public function onFlush(OnFlushEventArgs $args) |
150
|
|
|
{ |
151
|
|
|
$entitiesToDelete = $args->getEntityManager()->getUnitOfWork()->getScheduledEntityDeletions(); |
152
|
|
|
$entitiesToUpdate = $args->getEntityManager()->getUnitOfWork()->getScheduledEntityUpdates(); |
153
|
|
|
$extendProvider = $this->configManager->getProvider('extend'); |
154
|
|
|
if (!empty($entitiesToDelete) || !empty($entitiesToUpdate)) { |
155
|
|
|
foreach ($entitiesToDelete as $entity) { |
156
|
|
|
$class = $this->doctrineHelper->getEntityClass($entity); |
157
|
|
|
$entityIdentifiersByClassName = $this->doctrineHelper->getEntityIdentifierFieldNamesForClass($class); |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Skip entities where we can't get single identifier |
161
|
|
|
*/ |
162
|
|
|
if (count($entityIdentifiersByClassName) > 1) { |
163
|
|
|
return; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
$id = $this->doctrineHelper->getSingleEntityIdentifier($entity); |
167
|
|
|
$key = $class . '_' . $id; |
168
|
|
|
if (!isset($this->deletedEntities[$key]) |
169
|
|
|
&& $this->activityContactProvider->isSupportedEntity($class) |
170
|
|
|
) { |
171
|
|
|
$targets = $entity->getActivityTargetEntities(); |
172
|
|
|
$targetsInfo = []; |
173
|
|
View Code Duplication |
foreach ($targets as $target) { |
|
|
|
|
174
|
|
|
$targetClassName = ClassUtils::getClass($target); |
175
|
|
|
if (!TargetExcludeList::isExcluded($targetClassName) && |
176
|
|
|
$extendProvider->getConfig($targetClassName)->is('is_extend')) { |
177
|
|
|
$targetsInfo[] = [ |
178
|
|
|
'class' => $this->doctrineHelper->getEntityClass($target), |
179
|
|
|
'id' => $this->doctrineHelper->getSingleEntityIdentifier($target), |
180
|
|
|
'direction' => $this->activityContactProvider->getActivityDirection($entity, $target) |
181
|
|
|
]; |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
$this->deletedEntities[$key] = [ |
185
|
|
|
'class' => $class, |
186
|
|
|
'id' => $id, |
187
|
|
|
'contactDate' => $this->activityContactProvider->getActivityDate($entity), |
188
|
|
|
'targets' => $targetsInfo |
189
|
|
|
]; |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
foreach ($entitiesToUpdate as $entity) { |
194
|
|
|
$class = $this->doctrineHelper->getEntityClass($entity); |
195
|
|
|
$id = $this->doctrineHelper->getSingleEntityIdentifier($entity); |
196
|
|
|
$key = $class . '_' . $id; |
197
|
|
|
if (!isset($this->updatedEntities[$key]) |
198
|
|
|
&& $this->activityContactProvider->isSupportedEntity($class) |
199
|
|
|
) { |
200
|
|
|
$changes = $args->getEntityManager()->getUnitOfWork()->getEntityChangeSet($entity); |
201
|
|
|
$isDirectionChanged = $this->activityContactProvider |
202
|
|
|
->getActivityDirectionProvider($entity) |
203
|
|
|
->isDirectionChanged($changes); |
204
|
|
|
|
205
|
|
|
$targets = $entity->getActivityTargetEntities(); |
206
|
|
|
$targetsInfo = []; |
207
|
|
View Code Duplication |
foreach ($targets as $target) { |
|
|
|
|
208
|
|
|
$targetClassName = ClassUtils::getClass($target); |
209
|
|
|
if (!TargetExcludeList::isExcluded($targetClassName) && |
210
|
|
|
$extendProvider->getConfig($targetClassName)->is('is_extend')) { |
211
|
|
|
$targetsInfo[] = [ |
212
|
|
|
'class' => $this->doctrineHelper->getEntityClass($target), |
213
|
|
|
'id' => $this->doctrineHelper->getSingleEntityIdentifier($target), |
214
|
|
|
'direction' => $this->activityContactProvider->getActivityDirection($entity, $target), |
215
|
|
|
'is_direction_changed' => $isDirectionChanged |
216
|
|
|
]; |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
$this->updatedEntities[$key] = [ |
220
|
|
|
'class' => $class, |
221
|
|
|
'id' => $id, |
222
|
|
|
'contactDate' => $this->activityContactProvider->getActivityDate($entity), |
223
|
|
|
'targets' => $targetsInfo |
224
|
|
|
]; |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Save collected changes |
232
|
|
|
* |
233
|
|
|
* @param PostFlushEventArgs $args |
234
|
|
|
* @throws \Exception |
235
|
|
|
*/ |
236
|
|
|
public function postFlush(PostFlushEventArgs $args) |
237
|
|
|
{ |
238
|
|
|
$entityManager = $args->getEntityManager(); |
239
|
|
|
if (empty($this->deletedEntities) && empty($this->updatedEntities)) { |
240
|
|
|
return; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
$entityManager->beginTransaction(); |
244
|
|
|
try { |
245
|
|
|
$accessor = PropertyAccess::createPropertyAccessor(); |
246
|
|
|
$this->processDeletedEntities($entityManager, $accessor); |
247
|
|
|
$this->processUpdatedEntities($entityManager, $accessor); |
248
|
|
|
|
249
|
|
|
$entityManager->flush(); |
250
|
|
|
$entityManager->commit(); |
251
|
|
|
} catch (\Exception $e) { |
252
|
|
|
$entityManager->rollback(); |
253
|
|
|
throw $e; |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* @param EntityManager $em |
259
|
|
|
* @param PropertyAccessorInterface $accessor |
260
|
|
|
*/ |
261
|
|
|
protected function processDeletedEntities(EntityManager $em, PropertyAccessorInterface $accessor) |
262
|
|
|
{ |
263
|
|
|
foreach ($this->deletedEntities as $activityData) { |
264
|
|
|
foreach ($activityData['targets'] as $targetInfo) { |
265
|
|
|
$direction = $targetInfo['direction']; |
266
|
|
|
$target = $em->getRepository($targetInfo['class'])->find($targetInfo['id']); |
267
|
|
|
$accessor->setValue( |
268
|
|
|
$target, |
|
|
|
|
269
|
|
|
ActivityScope::CONTACT_COUNT, |
270
|
|
|
((int)$accessor->getValue($target, ActivityScope::CONTACT_COUNT) - 1) |
|
|
|
|
271
|
|
|
); |
272
|
|
|
|
273
|
|
|
$directionCountPath = ActivityScope::CONTACT_COUNT_OUT; |
274
|
|
|
$contactDatePath = ActivityScope::LAST_CONTACT_DATE_OUT; |
275
|
|
|
if ($direction === DirectionProviderInterface::DIRECTION_INCOMING) { |
276
|
|
|
$directionCountPath = ActivityScope::CONTACT_COUNT_IN; |
277
|
|
|
$contactDatePath = ActivityScope::LAST_CONTACT_DATE_IN; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
$accessor->setValue( |
281
|
|
|
$target, |
282
|
|
|
$directionCountPath, |
283
|
|
|
((int)$accessor->getValue($target, $directionCountPath) - 1) |
284
|
|
|
); |
285
|
|
|
|
286
|
|
|
$activityDate = $this->activityContactProvider->getLastContactActivityDate( |
287
|
|
|
$em, |
288
|
|
|
$target, |
|
|
|
|
289
|
|
|
$direction, |
290
|
|
|
$activityData['id'], |
291
|
|
|
$activityData['class'] |
292
|
|
|
); |
293
|
|
|
if ($activityDate) { |
|
|
|
|
294
|
|
|
$accessor->setValue($target, ActivityScope::LAST_CONTACT_DATE, $activityDate['all']); |
295
|
|
|
$accessor->setValue($target, $contactDatePath, $activityDate['direction']); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
$em->persist($target); |
|
|
|
|
299
|
|
|
} |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
$this->deletedEntities = []; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* @param EntityManager $em |
307
|
|
|
* @param PropertyAccessorInterface $accessor |
308
|
|
|
*/ |
309
|
|
|
protected function processUpdatedEntities(EntityManager $em, PropertyAccessorInterface $accessor) |
310
|
|
|
{ |
311
|
|
|
foreach ($this->updatedEntities as $activityData) { |
312
|
|
|
foreach ($activityData['targets'] as $targetInfo) { |
313
|
|
|
$direction = $targetInfo['direction']; |
314
|
|
|
$isDirectionChanged = $targetInfo['is_direction_changed']; |
315
|
|
|
$target = $em->getRepository($targetInfo['class'])->find($targetInfo['id']); |
316
|
|
|
/** process dates */ |
317
|
|
|
if ($direction === DirectionProviderInterface::DIRECTION_INCOMING) { |
318
|
|
|
$contactDatePath = ActivityScope::LAST_CONTACT_DATE_IN; |
319
|
|
|
$oppositeContactDatePath = ActivityScope::LAST_CONTACT_DATE_OUT; |
320
|
|
|
$oppositeDirection = DirectionProviderInterface::DIRECTION_OUTGOING; |
321
|
|
|
} else { |
322
|
|
|
$contactDatePath = ActivityScope::LAST_CONTACT_DATE_OUT; |
323
|
|
|
$oppositeContactDatePath = ActivityScope::LAST_CONTACT_DATE_IN; |
324
|
|
|
$oppositeDirection = DirectionProviderInterface::DIRECTION_INCOMING; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
$lastActivityDate = $this->activityContactProvider |
328
|
|
|
->getLastContactActivityDate($em, $target, $direction); |
|
|
|
|
329
|
|
|
if ($lastActivityDate) { |
|
|
|
|
330
|
|
|
$accessor->setValue($target, ActivityScope::LAST_CONTACT_DATE, $lastActivityDate['all']); |
|
|
|
|
331
|
|
|
$accessor->setValue($target, $contactDatePath, $lastActivityDate['direction']); |
332
|
|
|
$accessor->setValue( |
333
|
|
|
$target, |
334
|
|
|
$oppositeContactDatePath, |
335
|
|
|
$this->activityContactProvider->getLastContactActivityDate( |
336
|
|
|
$em, |
337
|
|
|
$target, |
|
|
|
|
338
|
|
|
$oppositeDirection |
339
|
|
|
)['direction'] |
340
|
|
|
); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** process counts (in case direction was changed) */ |
344
|
|
|
if ($isDirectionChanged) { |
345
|
|
|
list($oldDirection, $newDirection) = $this->getDirectionProperties($direction, $isDirectionChanged); |
346
|
|
|
$accessor->setValue( |
347
|
|
|
$target, |
|
|
|
|
348
|
|
|
$newDirection, |
349
|
|
|
(int)$accessor->getValue($target, $newDirection) + 1 |
|
|
|
|
350
|
|
|
); |
351
|
|
|
$accessor->setValue( |
352
|
|
|
$target, |
353
|
|
|
$oldDirection, |
354
|
|
|
(int)$accessor->getValue($target, $oldDirection) - 1 |
355
|
|
|
); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
$em->persist($target); |
|
|
|
|
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
$this->updatedEntities = []; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* @param string $currentDirection |
367
|
|
|
* @param bool $isDirectionChanged |
368
|
|
|
* |
369
|
|
|
* @return array Where first value is old property and second is new |
370
|
|
|
*/ |
371
|
|
|
protected function getDirectionProperties($currentDirection, $isDirectionChanged = false) |
372
|
|
|
{ |
373
|
|
|
if ($isDirectionChanged) { |
374
|
|
|
$properties = [ |
375
|
|
|
ActivityScope::CONTACT_COUNT_IN, |
376
|
|
|
ActivityScope::CONTACT_COUNT_OUT, |
377
|
|
|
]; |
378
|
|
|
|
379
|
|
|
return $currentDirection === DirectionProviderInterface::DIRECTION_INCOMING |
380
|
|
|
? array_reverse($properties) |
381
|
|
|
: $properties; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
return $currentDirection === DirectionProviderInterface::DIRECTION_INCOMING |
385
|
|
|
? [ActivityScope::CONTACT_COUNT_IN, ActivityScope::CONTACT_COUNT_IN] |
386
|
|
|
: [ActivityScope::CONTACT_COUNT_OUT, ActivityScope::CONTACT_COUNT_OUT]; |
387
|
|
|
} |
388
|
|
|
} |
389
|
|
|
|
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.