|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Oro\Bundle\ImapBundle\Sync; |
|
4
|
|
|
|
|
5
|
|
|
use Doctrine\Common\Collections\ArrayCollection; |
|
6
|
|
|
use Doctrine\ORM\EntityManager; |
|
7
|
|
|
use Doctrine\ORM\Query; |
|
8
|
|
|
|
|
9
|
|
|
use Oro\Bundle\BatchBundle\ORM\Query\BufferedQueryResultIterator; |
|
10
|
|
|
use Oro\Bundle\EmailBundle\Entity\Mailbox; |
|
11
|
|
|
use Oro\Bundle\EmailBundle\Model\FolderType; |
|
12
|
|
|
use Oro\Bundle\EmailBundle\Builder\EmailEntityBuilder; |
|
13
|
|
|
use Oro\Bundle\EmailBundle\Entity\Email as EmailEntity; |
|
14
|
|
|
use Oro\Bundle\EmailBundle\Entity\EmailFolder; |
|
15
|
|
|
use Oro\Bundle\EmailBundle\Entity\EmailOrigin; |
|
16
|
|
|
use Oro\Bundle\EmailBundle\Entity\EmailUser; |
|
17
|
|
|
use Oro\Bundle\EmailBundle\Sync\AbstractEmailSynchronizationProcessor; |
|
18
|
|
|
use Oro\Bundle\EmailBundle\Sync\KnownEmailAddressCheckerInterface; |
|
19
|
|
|
use Oro\Bundle\ImapBundle\Entity\ImapEmail; |
|
20
|
|
|
use Oro\Bundle\ImapBundle\Entity\ImapEmailFolder; |
|
21
|
|
|
use Oro\Bundle\ImapBundle\Entity\Repository\ImapEmailFolderRepository; |
|
22
|
|
|
use Oro\Bundle\ImapBundle\Entity\Repository\ImapEmailRepository; |
|
23
|
|
|
use Oro\Bundle\ImapBundle\Mail\Storage\Exception\UnsupportException; |
|
24
|
|
|
use Oro\Bundle\ImapBundle\Mail\Storage\Exception\UnselectableFolderException; |
|
25
|
|
|
use Oro\Bundle\ImapBundle\Mail\Storage\Folder; |
|
26
|
|
|
use Oro\Bundle\ImapBundle\Manager\ImapEmailIterator; |
|
27
|
|
|
use Oro\Bundle\ImapBundle\Manager\ImapEmailManager; |
|
28
|
|
|
use Oro\Bundle\ImapBundle\Manager\DTO\Email; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity) |
|
32
|
|
|
*/ |
|
33
|
|
|
class ImapEmailSynchronizationProcessor extends AbstractEmailSynchronizationProcessor |
|
34
|
|
|
{ |
|
35
|
|
|
/** Determines how many emails can be loaded from IMAP server at once */ |
|
36
|
|
|
const READ_BATCH_SIZE = 100; |
|
37
|
|
|
|
|
38
|
|
|
/** Determines how often "Processed X of N emails" hint should be added to a log */ |
|
39
|
|
|
const READ_HINT_COUNT = 500; |
|
40
|
|
|
|
|
41
|
|
|
/** Determines how often the clearing of outdated folders routine should be executed */ |
|
42
|
|
|
const CLEANUP_EVERY_N_RUN = 100; |
|
43
|
|
|
|
|
44
|
|
|
/** @var ImapEmailManager */ |
|
45
|
|
|
protected $manager; |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* Constructor |
|
49
|
|
|
* |
|
50
|
|
|
* @param EntityManager $em |
|
51
|
|
|
* @param EmailEntityBuilder $emailEntityBuilder |
|
52
|
|
|
* @param KnownEmailAddressCheckerInterface $knownEmailAddressChecker |
|
53
|
|
|
* @param ImapEmailManager $manager |
|
54
|
|
|
*/ |
|
55
|
|
|
public function __construct( |
|
56
|
|
|
EntityManager $em, |
|
57
|
|
|
EmailEntityBuilder $emailEntityBuilder, |
|
58
|
|
|
KnownEmailAddressCheckerInterface $knownEmailAddressChecker, |
|
59
|
|
|
ImapEmailManager $manager |
|
60
|
|
|
) { |
|
61
|
|
|
parent::__construct($em, $emailEntityBuilder, $knownEmailAddressChecker); |
|
62
|
|
|
$this->manager = $manager; |
|
63
|
|
|
} |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* {@inheritdoc} |
|
67
|
|
|
*/ |
|
68
|
|
|
public function process(EmailOrigin $origin, $syncStartTime) |
|
69
|
|
|
{ |
|
70
|
|
|
// make sure that the entity builder is empty |
|
71
|
|
|
$this->emailEntityBuilder->clear(); |
|
72
|
|
|
|
|
73
|
|
|
$this->initEnv($origin); |
|
74
|
|
|
|
|
75
|
|
|
// iterate through all folders enabled for sync and do a synchronization of emails for each one |
|
76
|
|
|
$imapFolders = $this->getSyncEnabledImapFolders($origin); |
|
77
|
|
|
foreach ($imapFolders as $imapFolder) { |
|
78
|
|
|
$folder = $imapFolder->getFolder(); |
|
79
|
|
|
|
|
80
|
|
|
// ask an email server to select the current folder |
|
81
|
|
|
$folderName = $folder->getFullName(); |
|
82
|
|
|
try { |
|
83
|
|
|
$this->manager->selectFolder($folderName); |
|
84
|
|
|
$this->logger->info(sprintf('The folder "%s" is selected.', $folderName)); |
|
85
|
|
|
|
|
86
|
|
|
// register the current folder in the entity builder |
|
87
|
|
|
$this->emailEntityBuilder->setFolder($folder); |
|
88
|
|
|
|
|
89
|
|
|
// sync emails using this search query |
|
90
|
|
|
$lastSynchronizedAt = $this->syncEmails($origin, $imapFolder); |
|
91
|
|
|
$folder->setSynchronizedAt($lastSynchronizedAt > $syncStartTime ? $lastSynchronizedAt : $syncStartTime); |
|
92
|
|
|
|
|
93
|
|
|
$startDate = $folder->getSynchronizedAt(); |
|
94
|
|
|
$checkStartDate = clone $startDate; |
|
95
|
|
|
$checkStartDate->modify('-6 month'); |
|
96
|
|
|
|
|
97
|
|
|
// set seen flags from previously synchronized emails |
|
98
|
|
|
$this->checkFlags($imapFolder, $checkStartDate); |
|
99
|
|
|
|
|
100
|
|
|
$this->em->flush($folder); |
|
101
|
|
|
} catch (UnselectableFolderException $e) { |
|
102
|
|
|
$this->logger->info(sprintf('The folder "%s" cannot be selected and was skipped.', $folderName)); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
$this->cleanUp(true, $imapFolder->getFolder()); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
$this->removeRemotelyRemovedEmails($origin); |
|
109
|
|
|
|
|
110
|
|
|
// run removing of empty outdated folders every N synchronizations |
|
111
|
|
|
if ($origin->getSyncCount() > 0 && $origin->getSyncCount() % self::CLEANUP_EVERY_N_RUN == 0) { |
|
112
|
|
|
$this->cleanupOutdatedFolders($origin); |
|
113
|
|
|
} |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
/** |
|
117
|
|
|
* @param EmailOrigin $origin |
|
118
|
|
|
*/ |
|
119
|
|
|
protected function removeRemotelyRemovedEmails(EmailOrigin $origin) |
|
120
|
|
|
{ |
|
121
|
|
|
$imapFolders = $this->getSyncEnabledImapFolders($origin); |
|
122
|
|
|
foreach ($imapFolders as $imapFolder) { |
|
123
|
|
|
$folder = $imapFolder->getFolder(); |
|
124
|
|
|
$folderName = $folder->getFullName(); |
|
125
|
|
|
try { |
|
126
|
|
|
$this->manager->selectFolder($folderName); |
|
127
|
|
|
|
|
128
|
|
|
$this->em->transactional(function () use ($imapFolder, $folder) { |
|
129
|
|
|
$existingUids = $this->manager->getEmailUIDs(); |
|
130
|
|
|
|
|
131
|
|
|
$staleImapEmailsQb = $this->em->getRepository('OroImapBundle:ImapEmail')->createQueryBuilder('ie'); |
|
132
|
|
|
$staleImapEmailsQb |
|
133
|
|
|
->andWhere($staleImapEmailsQb->expr()->eq('ie.imapFolder', ':imap_folder')) |
|
134
|
|
|
->setParameter('imap_folder', $imapFolder); |
|
135
|
|
|
|
|
136
|
|
|
if ($existingUids) { |
|
|
|
|
|
|
137
|
|
|
$staleImapEmailsQb |
|
138
|
|
|
->andWhere($staleImapEmailsQb->expr()->notIn('ie.uid', ':uids')) |
|
139
|
|
|
->setParameter('uids', $existingUids); |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
$staleImapEmails = (new BufferedQueryResultIterator($staleImapEmailsQb)) |
|
143
|
|
|
->setPageCallback(function () { |
|
144
|
|
|
$this->em->flush(); |
|
145
|
|
|
$this->em->clear(); |
|
146
|
|
|
}); |
|
147
|
|
|
|
|
148
|
|
|
/* @var $staleImapEmails ImapEmail[] */ |
|
149
|
|
|
foreach ($staleImapEmails as $imapEmail) { |
|
150
|
|
|
$email = $imapEmail->getEmail(); |
|
151
|
|
|
$email->getEmailUsers()->forAll(function ($key, EmailUser $emailUser) use ($folder) { |
|
152
|
|
|
$emailUser->removeFolder($folder); |
|
153
|
|
|
if (!$emailUser->getFolders()->count()) { |
|
154
|
|
|
$this->em->remove($emailUser); |
|
155
|
|
|
} |
|
156
|
|
|
}); |
|
157
|
|
|
$this->em->remove($imapEmail); |
|
158
|
|
|
} |
|
159
|
|
|
}); |
|
160
|
|
|
} catch (UnselectableFolderException $e) { |
|
161
|
|
|
$this->logger->info( |
|
162
|
|
|
sprintf('The folder "%s" cannot be selected for remove email and was skipped.', $folderName) |
|
163
|
|
|
); |
|
164
|
|
|
} |
|
165
|
|
|
} |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
/** |
|
169
|
|
|
* @param ImapEmailFolder $imapFolder |
|
170
|
|
|
* @param \DateTime $startDate |
|
171
|
|
|
*/ |
|
172
|
|
|
protected function checkFlags(ImapEmailFolder $imapFolder, $startDate) |
|
173
|
|
|
{ |
|
174
|
|
|
try { |
|
175
|
|
|
$uids = $this->manager->getUnseenEmailUIDs($startDate); |
|
176
|
|
|
|
|
177
|
|
|
$emailImapRepository = $this->em->getRepository('OroImapBundle:ImapEmail'); |
|
178
|
|
|
$emailUserRepository = $this->em->getRepository('OroEmailBundle:EmailUser'); |
|
179
|
|
|
|
|
180
|
|
|
$ids = $emailImapRepository->getEmailUserIdsByUIDs($uids, $imapFolder->getFolder(), $startDate); |
|
181
|
|
|
$invertedIds = $emailUserRepository->getInvertedIdsFromFolder($ids, $imapFolder->getFolder(), $startDate); |
|
182
|
|
|
|
|
183
|
|
|
$emailUserRepository->setEmailUsersSeen($ids, false); |
|
184
|
|
|
$emailUserRepository->setEmailUsersSeen($invertedIds, true); |
|
185
|
|
|
} catch (UnsupportException $e) { |
|
186
|
|
|
$this->logger->info(sprintf('Seen update unsupported - "%s"', $imapFolder->getFolder()->getOrigin())); |
|
187
|
|
|
} |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
|
|
/** |
|
191
|
|
|
* Deletes all empty outdated folders |
|
192
|
|
|
* |
|
193
|
|
|
* @param EmailOrigin $origin |
|
194
|
|
|
*/ |
|
195
|
|
|
protected function cleanupOutdatedFolders(EmailOrigin $origin) |
|
196
|
|
|
{ |
|
197
|
|
|
$this->logger->info('Removing empty outdated folders ...'); |
|
198
|
|
|
|
|
199
|
|
|
/** @var ImapEmailFolderRepository $repo */ |
|
200
|
|
|
$repo = $this->em->getRepository('OroImapBundle:ImapEmailFolder'); |
|
201
|
|
|
$imapFolders = $repo->getEmptyOutdatedFoldersByOrigin($origin); |
|
202
|
|
|
$folders = new ArrayCollection(); |
|
203
|
|
|
|
|
204
|
|
|
foreach ($imapFolders as $imapFolder) { |
|
205
|
|
|
$this->logger->info(sprintf('Remove "%s" folder.', $imapFolder->getFolder()->getFullName())); |
|
206
|
|
|
|
|
207
|
|
|
if (!$folders->contains($imapFolder->getFolder())) { |
|
208
|
|
|
$folders->add($imapFolder->getFolder()); |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
$this->em->remove($imapFolder); |
|
212
|
|
|
} |
|
213
|
|
|
|
|
214
|
|
|
foreach ($folders as $folder) { |
|
215
|
|
|
$this->em->remove($folder); |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
if (count($imapFolders) > 0) { |
|
219
|
|
|
$this->em->flush(); |
|
220
|
|
|
$this->logger->info(sprintf('Removed %d folder(s).', count($imapFolders))); |
|
221
|
|
|
} |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
/** |
|
225
|
|
|
* Gets the list of IMAP folders already stored in a database |
|
226
|
|
|
* The outdated folders are ignored |
|
227
|
|
|
* |
|
228
|
|
|
* @param EmailOrigin $origin |
|
229
|
|
|
* |
|
230
|
|
|
* @return ImapEmailFolder[] |
|
231
|
|
|
*/ |
|
232
|
|
View Code Duplication |
protected function getExistingImapFolders(EmailOrigin $origin) |
|
233
|
|
|
{ |
|
234
|
|
|
$this->logger->info('Loading existing folders ...'); |
|
235
|
|
|
|
|
236
|
|
|
/** @var ImapEmailFolderRepository $repo */ |
|
237
|
|
|
$repo = $this->em->getRepository('OroImapBundle:ImapEmailFolder'); |
|
238
|
|
|
$imapFolders = $repo->getFoldersByOrigin($origin); |
|
239
|
|
|
|
|
240
|
|
|
$this->logger->info(sprintf('Loaded %d folder(s).', count($imapFolders))); |
|
241
|
|
|
|
|
242
|
|
|
return $imapFolders; |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
/** |
|
246
|
|
|
* Gets the list of IMAP folders enabled for sync |
|
247
|
|
|
* The outdated folders are ignored |
|
248
|
|
|
* |
|
249
|
|
|
* @param EmailOrigin $origin |
|
250
|
|
|
* |
|
251
|
|
|
* @return ImapEmailFolder[] |
|
252
|
|
|
*/ |
|
253
|
|
View Code Duplication |
protected function getSyncEnabledImapFolders(EmailOrigin $origin) |
|
254
|
|
|
{ |
|
255
|
|
|
$this->logger->info('Get folders enabled for sync...'); |
|
256
|
|
|
|
|
257
|
|
|
/** @var ImapEmailFolderRepository $repo */ |
|
258
|
|
|
$repo = $this->em->getRepository('OroImapBundle:ImapEmailFolder'); |
|
259
|
|
|
$imapFolders = $repo->getFoldersByOrigin($origin, false, EmailFolder::SYNC_ENABLED_TRUE); |
|
260
|
|
|
|
|
261
|
|
|
$this->logger->info(sprintf('Got %d folder(s).', count($imapFolders))); |
|
262
|
|
|
|
|
263
|
|
|
return $imapFolders; |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
/** |
|
267
|
|
|
* Gets all folders from IMAP server |
|
268
|
|
|
* |
|
269
|
|
|
* @return Folder[] |
|
270
|
|
|
*/ |
|
271
|
|
|
protected function getFolders() |
|
272
|
|
|
{ |
|
273
|
|
|
$this->logger->info('Retrieving folders from an email server ...'); |
|
274
|
|
|
|
|
275
|
|
|
$srcFolders = $this->manager->getFolders(null, true); |
|
276
|
|
|
|
|
277
|
|
|
$folders = []; |
|
278
|
|
|
foreach ($srcFolders as $srcFolder) { |
|
279
|
|
|
if (!$srcFolder->isSelectable()) { |
|
280
|
|
|
continue; |
|
281
|
|
|
} |
|
282
|
|
|
if ($srcFolder->hasFlag([Folder::FLAG_DRAFTS, Folder::FLAG_SPAM, Folder::FLAG_TRASH, Folder::FLAG_ALL])) { |
|
283
|
|
|
continue; |
|
284
|
|
|
} |
|
285
|
|
|
|
|
286
|
|
|
$folders[] = $srcFolder; |
|
287
|
|
|
} |
|
288
|
|
|
|
|
289
|
|
|
$this->logger->info(sprintf('Retrieved %d folder(s).', count($folders))); |
|
290
|
|
|
|
|
291
|
|
|
return $folders; |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
/** |
|
295
|
|
|
* Gets UIDVALIDITY of the given folder |
|
296
|
|
|
* |
|
297
|
|
|
* @param Folder $folder |
|
298
|
|
|
* |
|
299
|
|
|
* @return int |
|
300
|
|
|
*/ |
|
301
|
|
|
protected function getUidValidity(Folder $folder) |
|
302
|
|
|
{ |
|
303
|
|
|
$this->manager->selectFolder($folder->getGlobalName()); |
|
304
|
|
|
|
|
305
|
|
|
return $this->manager->getUidValidity(); |
|
306
|
|
|
} |
|
307
|
|
|
|
|
308
|
|
|
/** |
|
309
|
|
|
* Performs synchronization of emails retrieved by the given search query in the given folder |
|
310
|
|
|
* |
|
311
|
|
|
* @param EmailOrigin $origin |
|
312
|
|
|
* @param ImapEmailFolder $imapFolder |
|
313
|
|
|
* |
|
314
|
|
|
* @return \DateTime The max sent date |
|
315
|
|
|
*/ |
|
316
|
|
|
protected function syncEmails(EmailOrigin $origin, ImapEmailFolder $imapFolder) |
|
317
|
|
|
{ |
|
318
|
|
|
$folder = $imapFolder->getFolder(); |
|
319
|
|
|
$lastSynchronizedAt = $folder->getSynchronizedAt(); |
|
320
|
|
|
$emails = $this->getEmailIterator($origin, $imapFolder, $folder); |
|
321
|
|
|
$count = $processed = $invalid = $totalInvalid = 0; |
|
322
|
|
|
$emails->setIterationOrder(true); |
|
323
|
|
|
$emails->setBatchSize(self::READ_BATCH_SIZE); |
|
324
|
|
|
$emails->setConvertErrorCallback( |
|
325
|
|
|
function (\Exception $e) use (&$invalid) { |
|
326
|
|
|
$invalid++; |
|
327
|
|
|
$this->logger->error( |
|
328
|
|
|
sprintf('Error occurred while trying to process email: %s', $e->getMessage()), |
|
329
|
|
|
['exception' => $e] |
|
330
|
|
|
); |
|
331
|
|
|
} |
|
332
|
|
|
); |
|
333
|
|
|
|
|
334
|
|
|
$this->logger->info(sprintf('Found %d email(s).', $emails->count())); |
|
335
|
|
|
|
|
336
|
|
|
$batch = []; |
|
337
|
|
|
/** @var Email $email */ |
|
338
|
|
|
foreach ($emails as $email) { |
|
339
|
|
|
$processed++; |
|
340
|
|
|
if ($processed % self::READ_HINT_COUNT === 0) { |
|
341
|
|
|
$this->logger->info( |
|
342
|
|
|
sprintf( |
|
343
|
|
|
'Processed %d of %d emails.%s', |
|
344
|
|
|
$processed, |
|
345
|
|
|
$emails->count(), |
|
346
|
|
|
$invalid === 0 ? '' : sprintf(' Detected %d invalid email(s).', $invalid) |
|
347
|
|
|
) |
|
348
|
|
|
); |
|
349
|
|
|
$totalInvalid += $invalid; |
|
350
|
|
|
$invalid = 0; |
|
351
|
|
|
} |
|
352
|
|
|
|
|
353
|
|
|
if ($email->getSentAt() > $lastSynchronizedAt) { |
|
354
|
|
|
$lastSynchronizedAt = $email->getSentAt(); |
|
355
|
|
|
} |
|
356
|
|
|
|
|
357
|
|
|
$count++; |
|
358
|
|
|
$batch[] = $email; |
|
359
|
|
|
if ($count === self::DB_BATCH_SIZE) { |
|
360
|
|
|
$this->saveEmails( |
|
361
|
|
|
$batch, |
|
362
|
|
|
$imapFolder |
|
363
|
|
|
); |
|
364
|
|
|
$count = 0; |
|
365
|
|
|
$batch = []; |
|
366
|
|
|
} |
|
367
|
|
|
} |
|
368
|
|
|
if ($count > 0) { |
|
369
|
|
|
$this->saveEmails( |
|
370
|
|
|
$batch, |
|
371
|
|
|
$imapFolder |
|
372
|
|
|
); |
|
373
|
|
|
} |
|
374
|
|
|
|
|
375
|
|
|
$totalInvalid += $invalid; |
|
376
|
|
|
if ($totalInvalid > 0) { |
|
377
|
|
|
$this->logger->warning( |
|
378
|
|
|
sprintf('Detected %d invalid email(s) in "%s" folder.', $totalInvalid, $folder->getFullName()) |
|
379
|
|
|
); |
|
380
|
|
|
} |
|
381
|
|
|
|
|
382
|
|
|
return $lastSynchronizedAt; |
|
383
|
|
|
} |
|
384
|
|
|
|
|
385
|
|
|
/** |
|
386
|
|
|
* Saves emails into the database |
|
387
|
|
|
* |
|
388
|
|
|
* @param Email[] $emails |
|
389
|
|
|
* @param ImapEmailFolder $imapFolder |
|
390
|
|
|
*/ |
|
391
|
|
|
protected function saveEmails(array $emails, ImapEmailFolder $imapFolder) |
|
392
|
|
|
{ |
|
393
|
|
|
$this->emailEntityBuilder->removeEmails(); |
|
394
|
|
|
|
|
395
|
|
|
$folder = $imapFolder->getFolder(); |
|
396
|
|
|
$existingUids = $this->getExistingUids($folder, $emails); |
|
397
|
|
|
$messageIds = $this->getMessageIds($emails); |
|
398
|
|
|
$existingImapEmails = $this->getExistingImapEmails($folder->getOrigin(), $messageIds); |
|
399
|
|
|
$existingEmailUsers = $this->getExistingEmailUsers($folder, $messageIds); |
|
400
|
|
|
/** @var ImapEmail[] $newImapEmails */ |
|
401
|
|
|
$newImapEmails = []; |
|
402
|
|
|
foreach ($emails as $email) { |
|
403
|
|
|
if (!$this->checkOnOldEmailForMailbox($folder, $email, $folder->getOrigin()->getMailbox())) { |
|
404
|
|
|
continue; |
|
405
|
|
|
} |
|
406
|
|
|
if (!$this->checkOnExistsSavedEmail($email, $existingUids)) { |
|
407
|
|
|
continue; |
|
408
|
|
|
} |
|
409
|
|
|
|
|
410
|
|
|
/** @var ImapEmail[] $relatedExistingImapEmails */ |
|
411
|
|
|
$relatedExistingImapEmails = array_filter( |
|
412
|
|
|
$existingImapEmails, |
|
413
|
|
|
function (ImapEmail $imapEmail) use ($email) { |
|
414
|
|
|
return $imapEmail->getEmail()->getMessageId() === $email->getMessageId(); |
|
415
|
|
|
} |
|
416
|
|
|
); |
|
417
|
|
|
|
|
418
|
|
|
try { |
|
419
|
|
|
if (!isset($existingEmailUsers[$email->getMessageId()])) { |
|
420
|
|
|
$emailUser = $this->addEmailUser( |
|
421
|
|
|
$email, |
|
422
|
|
|
$folder, |
|
423
|
|
|
$email->hasFlag("\\Seen"), |
|
424
|
|
|
$this->currentUser, |
|
425
|
|
|
$this->currentOrganization |
|
426
|
|
|
); |
|
427
|
|
|
} else { |
|
428
|
|
|
$emailUser = $existingEmailUsers[$email->getMessageId()]; |
|
429
|
|
|
if (!$emailUser->getFolders()->contains($folder)) { |
|
430
|
|
|
$emailUser->addFolder($folder); |
|
431
|
|
|
} |
|
432
|
|
|
} |
|
433
|
|
|
$imapEmail = $this->createImapEmail($email->getId()->getUid(), $emailUser->getEmail(), $imapFolder); |
|
434
|
|
|
$newImapEmails[] = $imapEmail; |
|
435
|
|
|
$this->em->persist($imapEmail); |
|
436
|
|
|
$this->logger->notice( |
|
437
|
|
|
sprintf( |
|
438
|
|
|
'The "%s" (UID: %d) email was persisted.', |
|
439
|
|
|
$email->getSubject(), |
|
440
|
|
|
$email->getId()->getUid() |
|
441
|
|
|
) |
|
442
|
|
|
); |
|
443
|
|
|
} catch (\Exception $e) { |
|
444
|
|
|
$this->logger->warning( |
|
445
|
|
|
sprintf( |
|
446
|
|
|
'Failed to persist "%s" (UID: %d) email. Error: %s', |
|
447
|
|
|
$email->getSubject(), |
|
448
|
|
|
$email->getId()->getUid(), |
|
449
|
|
|
$e->getMessage() |
|
450
|
|
|
) |
|
451
|
|
|
); |
|
452
|
|
|
} |
|
453
|
|
|
|
|
454
|
|
|
$this->removeEmailFromOutdatedFolders($relatedExistingImapEmails); |
|
455
|
|
|
} |
|
456
|
|
|
|
|
457
|
|
|
$this->emailEntityBuilder->getBatch()->persist($this->em); |
|
458
|
|
|
|
|
459
|
|
|
// update references if needed |
|
460
|
|
|
$changes = $this->emailEntityBuilder->getBatch()->getChanges(); |
|
461
|
|
|
foreach ($newImapEmails as $imapEmail) { |
|
462
|
|
|
foreach ($changes as $change) { |
|
463
|
|
|
if ($change['old'] instanceof EmailEntity && $imapEmail->getEmail() === $change['old']) { |
|
464
|
|
|
$imapEmail->setEmail($change['new']); |
|
465
|
|
|
} |
|
466
|
|
|
} |
|
467
|
|
|
} |
|
468
|
|
|
$this->em->flush(); |
|
469
|
|
|
|
|
470
|
|
|
$this->cleanUp(); |
|
471
|
|
|
} |
|
472
|
|
|
|
|
473
|
|
|
/** |
|
474
|
|
|
* Check allowing to save email by date |
|
475
|
|
|
* |
|
476
|
|
|
* @param EmailFolder $folder |
|
477
|
|
|
* @param Email $email |
|
478
|
|
|
* @param Mailbox $mailbox |
|
479
|
|
|
* |
|
480
|
|
|
* @return bool |
|
481
|
|
|
*/ |
|
482
|
|
|
protected function checkOnOldEmailForMailbox(EmailFolder $folder, Email $email, $mailbox) |
|
483
|
|
|
{ |
|
484
|
|
|
/** |
|
485
|
|
|
* @description Will select max of those dates because emails in folder `sent` could have no received date |
|
486
|
|
|
* or same date. |
|
487
|
|
|
*/ |
|
488
|
|
|
$dateForCheck = max($email->getReceivedAt(), $email->getSentAt()); |
|
489
|
|
|
|
|
490
|
|
|
if ($mailbox && $folder->getSyncStartDate() > $dateForCheck) { |
|
491
|
|
|
$this->logger->info( |
|
492
|
|
|
sprintf( |
|
493
|
|
|
'Skip "%s" (UID: %d) email, because it was sent earlier than the start synchronization is set', |
|
494
|
|
|
$email->getSubject(), |
|
495
|
|
|
$email->getId()->getUid() |
|
496
|
|
|
) |
|
497
|
|
|
); |
|
498
|
|
|
|
|
499
|
|
|
return false; |
|
500
|
|
|
} |
|
501
|
|
|
|
|
502
|
|
|
return true; |
|
503
|
|
|
} |
|
504
|
|
|
|
|
505
|
|
|
/** |
|
506
|
|
|
* Check allowing to save email by uid |
|
507
|
|
|
* |
|
508
|
|
|
* @param Email $email |
|
509
|
|
|
* @param array $existingUids |
|
510
|
|
|
* |
|
511
|
|
|
* @return bool |
|
512
|
|
|
*/ |
|
513
|
|
|
protected function checkOnExistsSavedEmail(Email $email, array $existingUids) |
|
514
|
|
|
{ |
|
515
|
|
|
if (in_array($email->getId()->getUid(), $existingUids)) { |
|
516
|
|
|
$this->logger->info( |
|
517
|
|
|
sprintf( |
|
518
|
|
|
'Skip "%s" (UID: %d) email, because it is already synchronised.', |
|
519
|
|
|
$email->getSubject(), |
|
520
|
|
|
$email->getId()->getUid() |
|
521
|
|
|
) |
|
522
|
|
|
); |
|
523
|
|
|
return false; |
|
524
|
|
|
} |
|
525
|
|
|
|
|
526
|
|
|
return true; |
|
527
|
|
|
} |
|
528
|
|
|
|
|
529
|
|
|
/** |
|
530
|
|
|
* Removes email from all outdated folders |
|
531
|
|
|
* |
|
532
|
|
|
* @param ImapEmail[] $imapEmails The list of all related IMAP emails |
|
533
|
|
|
*/ |
|
534
|
|
|
protected function removeEmailFromOutdatedFolders(array $imapEmails) |
|
535
|
|
|
{ |
|
536
|
|
|
/** @var ImapEmail[] $outdatedImapEmails */ |
|
537
|
|
|
$outdatedImapEmails = array_filter( |
|
538
|
|
|
$imapEmails, |
|
539
|
|
|
function (ImapEmail $imapEmail) { |
|
540
|
|
|
return $imapEmail->getImapFolder()->getFolder()->isOutdated(); |
|
541
|
|
|
} |
|
542
|
|
|
); |
|
543
|
|
|
foreach ($outdatedImapEmails as $imapEmail) { |
|
544
|
|
|
$this->removeImapEmailReference($imapEmail); |
|
545
|
|
|
} |
|
546
|
|
|
} |
|
547
|
|
|
|
|
548
|
|
|
/** |
|
549
|
|
|
* Removes an email from a folder linked to the given IMAP email object |
|
550
|
|
|
* |
|
551
|
|
|
* @param ImapEmail $imapEmail |
|
552
|
|
|
*/ |
|
553
|
|
|
protected function removeImapEmailReference(ImapEmail $imapEmail) |
|
554
|
|
|
{ |
|
555
|
|
|
$this->logger->info( |
|
556
|
|
|
sprintf( |
|
557
|
|
|
'Remove "%s" (UID: %d) email from "%s".', |
|
558
|
|
|
$imapEmail->getEmail()->getSubject(), |
|
559
|
|
|
$imapEmail->getUid(), |
|
560
|
|
|
$imapEmail->getImapFolder()->getFolder()->getFullName() |
|
561
|
|
|
) |
|
562
|
|
|
); |
|
563
|
|
|
|
|
564
|
|
|
$emailUser = $imapEmail->getEmail()->getEmailUserByFolder($imapEmail->getImapFolder()->getFolder()); |
|
565
|
|
|
if ($emailUser != null) { |
|
566
|
|
|
$imapEmail->getEmail()->getEmailUsers()->removeElement($emailUser); |
|
567
|
|
|
} |
|
568
|
|
|
$this->em->remove($imapEmail); |
|
569
|
|
|
} |
|
570
|
|
|
|
|
571
|
|
|
/** |
|
572
|
|
|
* Gets the list of UIDs of emails already exist in a database |
|
573
|
|
|
* |
|
574
|
|
|
* @param EmailFolder $folder |
|
575
|
|
|
* @param Email[] $emails |
|
576
|
|
|
* |
|
577
|
|
|
* @return int[] array if UIDs |
|
578
|
|
|
*/ |
|
579
|
|
|
protected function getExistingUids(EmailFolder $folder, array $emails) |
|
580
|
|
|
{ |
|
581
|
|
|
if (empty($emails)) { |
|
582
|
|
|
return []; |
|
583
|
|
|
} |
|
584
|
|
|
|
|
585
|
|
|
$uids = array_map( |
|
586
|
|
|
function ($el) { |
|
587
|
|
|
/** @var Email $el */ |
|
588
|
|
|
return $el->getId()->getUid(); |
|
589
|
|
|
}, |
|
590
|
|
|
$emails |
|
591
|
|
|
); |
|
592
|
|
|
|
|
593
|
|
|
/** @var ImapEmailRepository $repo */ |
|
594
|
|
|
$repo = $this->em->getRepository('OroImapBundle:ImapEmail'); |
|
595
|
|
|
|
|
596
|
|
|
return $repo->getExistingUids($folder, $uids); |
|
597
|
|
|
} |
|
598
|
|
|
|
|
599
|
|
|
/** |
|
600
|
|
|
* Gets the list of IMAP emails by Message-ID |
|
601
|
|
|
* |
|
602
|
|
|
* @param EmailOrigin $origin |
|
603
|
|
|
* @param string[] $messageIds |
|
604
|
|
|
* |
|
605
|
|
|
* @return ImapEmail[] |
|
606
|
|
|
*/ |
|
607
|
|
|
protected function getExistingImapEmails(EmailOrigin $origin, array $messageIds) |
|
608
|
|
|
{ |
|
609
|
|
|
if (empty($messageIds)) { |
|
610
|
|
|
return []; |
|
611
|
|
|
} |
|
612
|
|
|
/** @var ImapEmailRepository $repo */ |
|
613
|
|
|
$repo = $this->em->getRepository('OroImapBundle:ImapEmail'); |
|
614
|
|
|
|
|
615
|
|
|
return $repo->getEmailsByMessageIds($origin, $messageIds); |
|
616
|
|
|
} |
|
617
|
|
|
|
|
618
|
|
|
/** |
|
619
|
|
|
* Gets the list of Message-IDs for emails |
|
620
|
|
|
* |
|
621
|
|
|
* @param Email[] $emails |
|
622
|
|
|
* |
|
623
|
|
|
* @return string[] |
|
624
|
|
|
*/ |
|
625
|
|
|
protected function getMessageIds(array $emails) |
|
626
|
|
|
{ |
|
627
|
|
|
$result = []; |
|
628
|
|
|
foreach ($emails as $email) { |
|
629
|
|
|
$result[] = $email->getMessageId(); |
|
630
|
|
|
} |
|
631
|
|
|
|
|
632
|
|
|
return $result; |
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
/** |
|
636
|
|
|
* Creates new ImapEmail object |
|
637
|
|
|
* |
|
638
|
|
|
* @param int $uid |
|
639
|
|
|
* @param EmailEntity $email |
|
640
|
|
|
* @param ImapEmailFolder $imapFolder |
|
641
|
|
|
* |
|
642
|
|
|
* @return ImapEmail |
|
643
|
|
|
*/ |
|
644
|
|
|
protected function createImapEmail($uid, EmailEntity $email, ImapEmailFolder $imapFolder) |
|
645
|
|
|
{ |
|
646
|
|
|
$imapEmail = new ImapEmail(); |
|
647
|
|
|
$imapEmail |
|
648
|
|
|
->setUid($uid) |
|
649
|
|
|
->setEmail($email) |
|
650
|
|
|
->setImapFolder($imapFolder); |
|
651
|
|
|
|
|
652
|
|
|
return $imapEmail; |
|
653
|
|
|
} |
|
654
|
|
|
|
|
655
|
|
|
/** |
|
656
|
|
|
* Get email ids and create iterator |
|
657
|
|
|
* |
|
658
|
|
|
* @param EmailOrigin $origin |
|
659
|
|
|
* @param ImapEmailFolder $imapFolder |
|
660
|
|
|
* @param EmailFolder $folder |
|
661
|
|
|
* |
|
662
|
|
|
* @return ImapEmailIterator |
|
663
|
|
|
*/ |
|
664
|
|
|
protected function getEmailIterator( |
|
665
|
|
|
EmailOrigin $origin, |
|
666
|
|
|
ImapEmailFolder $imapFolder, |
|
667
|
|
|
EmailFolder $folder |
|
668
|
|
|
) { |
|
669
|
|
|
$lastUid = $this->em->getRepository('OroImapBundle:ImapEmail')->findLastUidByFolder($imapFolder); |
|
670
|
|
|
if (!$lastUid && $origin->getMailbox() && $folder->getSyncStartDate()) { |
|
671
|
|
|
$emails = $this->initialMailboxSync($folder); |
|
672
|
|
|
} else { |
|
673
|
|
|
$this->logger->info(sprintf('Previous max email UID "%s"', $lastUid)); |
|
674
|
|
|
$emails = $this->manager->getEmailsUidBased($lastUid); |
|
675
|
|
|
} |
|
676
|
|
|
|
|
677
|
|
|
return $emails; |
|
678
|
|
|
} |
|
679
|
|
|
|
|
680
|
|
|
/** |
|
681
|
|
|
* @param ImapEmail|null $existingImapEmail |
|
682
|
|
|
* @param bool $isMultiFolder |
|
683
|
|
|
* @param Email $email |
|
684
|
|
|
* |
|
685
|
|
|
* @return bool |
|
686
|
|
|
*/ |
|
687
|
|
|
protected function isMovableToOtherFolder($existingImapEmail, $isMultiFolder, $email) |
|
688
|
|
|
{ |
|
689
|
|
|
return !$isMultiFolder |
|
690
|
|
|
&& $existingImapEmail |
|
691
|
|
|
&& $email->getId()->getUid() === $existingImapEmail->getUid(); |
|
692
|
|
|
} |
|
693
|
|
|
|
|
694
|
|
|
/** |
|
695
|
|
|
* First system mailbox sync from sync start date |
|
696
|
|
|
* |
|
697
|
|
|
* @param EmailFolder $folder |
|
698
|
|
|
* |
|
699
|
|
|
* @return ImapEmailIterator |
|
700
|
|
|
*/ |
|
701
|
|
|
protected function initialMailboxSync(EmailFolder $folder) |
|
702
|
|
|
{ |
|
703
|
|
|
// build search query for emails sync |
|
704
|
|
|
$sqb = $this->manager->getSearchQueryBuilder(); |
|
705
|
|
|
if ($folder->getType() === FolderType::SENT) { |
|
706
|
|
|
$sqb->sent($folder->getSyncStartDate()); |
|
|
|
|
|
|
707
|
|
|
} else { |
|
708
|
|
|
$sqb->received($folder->getSyncStartDate()); |
|
|
|
|
|
|
709
|
|
|
} |
|
710
|
|
|
$searchQuery = $sqb->get(); |
|
711
|
|
|
$this->logger->info(sprintf('Loading emails from "%s" folder ...', $folder->getFullName())); |
|
712
|
|
|
$this->logger->info(sprintf('Query: "%s".', $searchQuery->convertToSearchString())); |
|
713
|
|
|
$emails = $this->manager->getEmails($searchQuery); |
|
714
|
|
|
|
|
715
|
|
|
return $emails; |
|
716
|
|
|
} |
|
717
|
|
|
} |
|
718
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)or! empty(...)instead.