Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
mailboxconnector.php
1<?php
2
4
13
15{
16 private const STANDARD_ERROR_KEY = 1;
17 private const LIMIT_ERROR_KEY = 2;
18 private const OAUTH_ERROR_KEY = 3;
19 private const EXISTS_ERROR_KEY = 4;
20 private const NO_MAIL_SERVICES_ERROR_KEY = 5;
21 private const SMTP_PASS_BAD_SYMBOLS_ERROR_KEY = 6;
22 public const CRM_MAX_AGE = 7;
23 public const MESSAGE_MAX_AGE = 7;
24
25 private bool $isSuccess = false;
26
27 private array $errorCollection = [];
28
29 private bool $isSMTPAvailable = false;
30
31 public function getSuccess(): bool
32 {
33 return $this->isSuccess;
34 }
35
36 public function setSuccess(): void
37 {
38 $this->isSuccess = true;
39 }
40
41 public function getErrors(): array
42 {
43 return $this->errorCollection;
44 }
45
46 protected function addError(string $error): void
47 {
48 $this->errorCollection[] = new Main\Error($error);
49 }
50
51 protected function addErrors(
52 Main\ErrorCollection $errors,
53 bool $isOAuth = false,
54 bool $isSender = false
55 ): void
56 {
57 $messages = [];
58 $details = [];
59
60 foreach ($errors as $item)
61 {
62 if ($item->getCode() < 0)
63 {
64 $details[] = $item;
65 }
66 else
67 {
68 $messages[] = $item;
69 }
70 }
71
72 if (count($messages) == 1 && reset($messages)->getCode() == Mail\Imap::ERR_AUTH)
73 {
74 $authError = Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_IMAP_AUTH_ERR_EXT');
75 if ($isOAuth && Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_ERR_OAUTH'))
76 {
77 $authError = Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_ERR_OAUTH');
78 }
79 if ($isOAuth && $isSender && Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_ERR_OAUTH_SMTP'))
80 {
81 $authError = Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_ERR_OAUTH_SMTP');
82 }
83
84 $messages = [
85 new Main\Error($authError, Mail\Imap::ERR_AUTH),
86 ];
87
88 $moreDetailsSection = false;
89 }
90 else
91 {
92 $moreDetailsSection = true;
93 }
94
95 $reduce = function($error)
96 {
97 return $error->getMessage();
98 };
99
100 if($moreDetailsSection)
101 {
102 $this->errorCollection[] = new Main\Error(
103 implode(': ', array_map($reduce, $messages)),
104 0,
105 implode(': ', array_map($reduce, $details))
106 );
107 }
108 else
109 {
110 $this->errorCollection[] = new Main\Error(
111 implode(': ', array_map($reduce, $messages)),
112 0,
113 );
114 }
115 }
116
117 private function setError(int $code = self::STANDARD_ERROR_KEY): void
118 {
119 switch ($code) {
120 case self::STANDARD_ERROR_KEY:
121 $this->addError(Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_FORM_ERROR'));
122 break;
123 case self::LIMIT_ERROR_KEY:
124 $this->addError(Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_LIMIT_ERROR'));
125 break;
126 case self::OAUTH_ERROR_KEY:
127 $this->addError(Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_OAUTH_ERROR'));
128 break;
129 case self::EXISTS_ERROR_KEY:
130 $this->addError(Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_EMAIL_EXISTS_ERROR'));
131 break;
132 case self::NO_MAIL_SERVICES_ERROR_KEY:
133 $this->addError(Loc::getMessage('MAIL_MAILBOX_CONNECTOR_CLIENT_THERE_ARE_NO_MAIL_SERVICES'));
134 break;
135 case self::SMTP_PASS_BAD_SYMBOLS_ERROR_KEY:
136 $this->addError(Loc::getMessage('MAIL_MAILBOX_CONNECTOR_SMTP_PASS_BAD_SYMBOLS'));
137 break;
138 }
139 }
140
141 private static function getUserOwnedMailboxCount()
142 {
143 global $USER;
144
145 $res = Mail\MailboxTable::getList([
146 'select' => [
147 new Main\Entity\ExpressionField('OWNED', 'COUNT(%s)', 'ID'),
148 ],
149 'filter' => [
150 '=ACTIVE' => 'Y',
151 '=USER_ID' => $USER->getId(),
152 '=SERVER_TYPE' => 'imap',
153 ],
154 ])->fetch();
155
156 return $res['OWNED'];
157 }
158
159 public static function canConnectNewMailbox(): bool
160 {
161 $userMailboxesLimit = LicenseManager::getUserMailboxesLimit();
162 if ($userMailboxesLimit >= 0)
163 {
164 if (self::getUserOwnedMailboxCount() >= $userMailboxesLimit)
165 {
166 return false;
167 }
168 }
169
170 return true;
171 }
172
173 private function syncMailbox(int $mailboxId): void
174 {
175 Main\Application::getInstance()->addBackgroundJob(function ($mailboxId) {
176 $mailboxHelper = Mailbox::createInstance($mailboxId, false);
177 $mailboxHelper->sync();
178 },[$mailboxId]);
179 }
180
181 private function setIsSmtpAvailable(): void
182 {
183 $defaultMailConfiguration = Configuration::getValue("smtp");
184 $this->isSMTPAvailable = Main\ModuleManager::isModuleInstalled('bitrix24')
185 || $defaultMailConfiguration['enabled'];
186 }
187
188 private function getSmtpAvailable(): bool
189 {
190 return $this->isSMTPAvailable;
191 }
192
193 public static function isValidMailHost(string $host): bool
194 {
195 if (\Bitrix\Main\ModuleManager::isModuleInstalled('bitrix24'))
196 {
197 // Private addresses can't be used in the cloud
198 $ip = \Bitrix\Main\Web\IpAddress::createByName($host);
199 if ($ip->isPrivate())
200 {
201 return false;
202 }
203 }
204
205 return true;
206 }
207
216 public static function appendSender(array $senderFields, string $userPrincipalName): array
217 {
218 $result = Main\Mail\Sender::add($senderFields);
219
220 if (empty($result['confirmed']) && $userPrincipalName)
221 {
222 $address = new Address($userPrincipalName);
223 $currentSmtpLogin = $senderFields['OPTIONS']['smtp']['login'] ?? '';
224 if ($currentSmtpLogin && $currentSmtpLogin !== $userPrincipalName && $address->validate())
225 {
226 // outlook workaround, sometimes SMTP auth only works with userPrincipalName
227 $senderFields['OPTIONS']['smtp']['login'] = $userPrincipalName;
228 $result = Main\Mail\Sender::add($senderFields);
229 }
230 }
231 return $result;
232 }
233
234 public function connectMailbox(
235 string $login = '',
236 string $password = '',
237 int $serviceId = 0,
238 string $server = '',
239 int $port = 993,
240 bool $ssl = true,
241 string $storageOauthUid = '',
242 bool $syncAfterConnection = true,
243 bool $useSmtp = true,
244 string $serverSmtp = '',
245 int $portSmtp = 587,
246 bool $sslSmtp = true,
247 string $loginSmtp = '',
248 string $passwordSMTP = '',
249 ): array
250 {
251 $login = trim($login);
252 $password = trim($password);
253 $server = trim($server);
254
255 $currentSite = \CSite::getById(SITE_ID)->fetch();
256 global $USER;
257
258 $this->setIsSmtpAvailable();
259
260 $service = Mail\MailServicesTable::getList([
261 'filter' => [
262 '=ID' => $serviceId,
263 'SERVICE_TYPE' => 'imap',
264 ],
265 ])->fetch();
266
267 if (empty($service))
268 {
269 $this->setError(self::NO_MAIL_SERVICES_ERROR_KEY);
270 return [];
271 }
272
273 if ($service['ACTIVE'] !== 'Y')
274 {
275 $this->setError();
276 return [];
277 }
278
279 if (!$this->canConnectNewMailbox())
280 {
281 $this->setError(self::LIMIT_ERROR_KEY);
282 return [];
283 }
284
285 if ($ssl)
286 {
287 $ssl = 'Y';
288 }
289 else
290 {
291 $ssl = 'N';
292 }
293
294 if ($sslSmtp)
295 {
296 $sslSmtp = 'Y';
297 }
298 else
299 {
300 $sslSmtp = 'N';
301 }
302
303 $mailboxData = [
304 'USERNAME' => '',
305 'SERVER' => $service['SERVER'] ?: trim($server),
306 'PORT' => $service['PORT'] ?: $port,
307 'USE_TLS' => $service['ENCRYPTION'] ?: $ssl,
308 'LINK' => $service['LINK'],
309 'EMAIL' => $login,
310 'NAME' => $login,
311 'PERIOD_CHECK' => 60 * 24,
312 'OPTIONS' => [
313 'flags' => [],
314 'sync_from' => time(),
315 'crm_sync_from' => time(),
316 'activateSync' => false,
317 'name' => '',
318 ],
319 ];
320
321 if ('N' == $service['UPLOAD_OUTGOING'] || empty($service['UPLOAD_OUTGOING']))
322 {
323 $mailboxData['OPTIONS']['flags'][] = 'deny_upload';
324 }
325
326 $isOAuth = false;
327 if ($storageOauthUid !== '' && $oauthHelper = Mail\MailServicesTable::getOAuthHelper($service))
328 {
329 $oauthHelper->getStoredToken($storageOauthUid);
330 $mailboxData['LOGIN'] = $mailboxData['EMAIL'];
331 $mailboxData['PASSWORD'] = $oauthHelper->buildMeta();
332 $isOAuth = true;
333 }
334 else
335 {
336 $mailboxData['LOGIN'] = $login;
337 $mailboxData['PASSWORD'] = $password;
338 }
339
340 if (empty($mailbox['EMAIL']))
341 {
342 $address = new Address($mailboxData['EMAIL']);
343 if (!$address->validate())
344 {
345 $this->setError(self::OAUTH_ERROR_KEY);
346 return [];
347 }
348
349 $mailboxData['EMAIL'] = $address->getEmail();
350 $this->email = $mailboxData['EMAIL'];
351 }
352 else
353 {
354 $this->email = $mailbox['EMAIL'];
355 }
356
357 if (empty($mailbox))
358 {
359 $mailbox = Mail\MailboxTable::getList([
360 'filter' => [
361 '=EMAIL' => $mailboxData['EMAIL'],
362 '=USER_ID' => $USER->getId(),
363 '=ACTIVE' => 'Y',
364 '=LID' => $currentSite['LID'],
365 ],
366 ])->fetch();
367
368 if (!empty($mailbox))
369 {
370 $this->setError(self::EXISTS_ERROR_KEY);
371 return [];
372 }
373 }
374
375 if (empty($mailboxData['NAME']))
376 {
377 $mailboxData['NAME'] = $mailboxData['EMAIL'];
378 }
379
380 if (!in_array($mailboxData['USE_TLS'], array('Y', 'S')))
381 {
382 $mailboxData['USE_TLS'] = 'N';
383 }
384
385 $unseen = Mail\Helper::getImapUnseen($mailboxData, 'inbox', $error, $errors);
386 if ($unseen === false)
387 {
388 if ($errors instanceof Main\ErrorCollection)
389 {
390 $this->addErrors($errors, $isOAuth);
391 }
392 else
393 {
394 $this->setError();
395 }
396 return [];
397 }
398
399 $useSmtp = $useSmtp || !empty(MailServicesTable::getOAuthHelper($service));
400
401 if ($this->getSmtpAvailable() && !$useSmtp && !empty($mailbox))
402 {
403 $res = Main\Mail\Internal\SenderTable::getList(array(
404 'filter' => array(
405 'IS_CONFIRMED' => true,
406 '=EMAIL' => $mailboxData['EMAIL'],
407 ),
408 ));
409 while ($item = $res->fetch())
410 {
411 if (!empty($item['OPTIONS']['smtp']['server']))
412 {
413 unset($item['OPTIONS']['smtp']);
414 Main\Mail\Internal\SenderTable::update(
415 $item['ID'],
416 array(
417 'OPTIONS' => $item['OPTIONS'],
418 )
419 );
420 }
421 }
422
423 Main\Mail\Sender::clearCustomSmtpCache($mailboxData['EMAIL']);
424 }
425
426 if ($this->getSmtpAvailable() && $useSmtp)
427 {
428 $senderFields = [
429 'NAME' => $mailboxData['USERNAME'],
430 'EMAIL' => $mailboxData['EMAIL'],
431 'USER_ID' => $USER->getId(),
432 'IS_CONFIRMED' => false,
433 'IS_PUBLIC' => false,
434 'OPTIONS' => [
435 'source' => 'mail.client.config',
436 ],
437 ];
438
439 $res = Main\Mail\Internal\SenderTable::getList(array(
440 'filter' => [
441 'IS_CONFIRMED' => true,
442 '=EMAIL' => $mailboxData['EMAIL'],
443 ],
444 'order' => [
445 'ID' => 'DESC',
446 ],
447 ));
448
449 while ($item = $res->fetch())
450 {
451 if (empty($smtpConfirmed))
452 {
453 if (!empty($item['OPTIONS']['smtp']['server']) && empty($item['OPTIONS']['smtp']['encrypted']))
454 {
455 $smtpConfirmed = $item['OPTIONS']['smtp'];
456 }
457 }
458
459 if ($senderFields['USER_ID'] == $item['USER_ID'] && $senderFields['NAME'] == $item['NAME'])
460 {
461 $senderFields = $item;
462 $senderFields['IS_CONFIRMED'] = false;
463 $senderFields['OPTIONS']['__replaces'] = $item['ID'];
464
465 unset($senderFields['ID']);
466
467 if (!empty($smtpConfirmed))
468 {
469 break;
470 }
471 }
472 }
473 }
474
475 if (!empty($senderFields))
476 {
477 $smtpConfig = array(
478 'server' => $service['SMTP_SERVER'] ?: trim($serverSmtp),
479 'port' => $service['SMTP_PORT'] ?: $portSmtp,
480 'protocol' => ('Y' == ($service['SMTP_ENCRYPTION'] ?: $sslSmtp) ? 'smtps' : 'smtp'),
481 'login' => $service['SMTP_LOGIN_AS_IMAP'] == 'Y' ? $mailboxData['LOGIN'] : $loginSmtp,
482 'password' => '',
483 );
484
485 if (!empty($smtpConfirmed) && is_array($smtpConfirmed))
486 {
487 // server, port, protocol, login, password
488 $smtpConfig = array_filter($smtpConfig) + $smtpConfirmed;
489 }
490
491 if ($service['SMTP_PASSWORD_AS_IMAP'] == 'Y')
492 {
493 $smtpConfig['password'] = $mailboxData['PASSWORD'];
494 $smtpConfig['isOauth'] = !empty($storageOauthUid);
495 }
496 else if ($passwordSMTP <> '')
497 {
498 if (preg_match('/^\^/', $passwordSMTP))
499 {
500 $this->setError(self::SMTP_PASS_BAD_SYMBOLS_ERROR_KEY);
501 return [];
502 }
503 else if (preg_match('/\x00/', $passwordSMTP))
504 {
505 $this->setError(self::SMTP_PASS_BAD_SYMBOLS_ERROR_KEY);
506 return [];
507 }
508
509 $smtpConfig['password'] = $passwordSMTP;
510 }
511
512 if (!$service['SMTP_SERVER'])
513 {
514 $regex = '/^(?:(?:http|https|ssl|tls|smtp):\/\/)?((?:[a-z0-9](?:-*[a-z0-9])*\.?)+)$/i';
515 if (!preg_match($regex, $smtpConfig['server'], $matches) && $matches[1] <> '')
516 {
517 $this->setError(self::OAUTH_ERROR_KEY);
518 return [];
519 }
520
521 $smtpConfig['server'] = $matches[1];
522
523 if (!self::isValidMailHost($smtpConfig['server']))
524 {
525 $this->setError(self::OAUTH_ERROR_KEY);
526 return [];
527 }
528 }
529
530 if (!$service['SMTP_PORT'])
531 {
532 if ($smtpConfig['port'] <= 0 || $smtpConfig['port'] > 65535)
533 {
534 $this->setError(self::OAUTH_ERROR_KEY);
535 return [];
536 }
537 }
538
539 $senderFields['OPTIONS']['smtp'] = $smtpConfig;
540
541 if (!empty($smtpConfirmed))
542 {
543 $senderFields['IS_CONFIRMED'] = !array_diff(
544 array('server', 'port', 'protocol', 'login', 'password', 'isOauth'),
545 array_keys(array_intersect_assoc($smtpConfig, $smtpConfirmed))
546 );
547 }
548 }
549
550 if (Main\Loader::includeModule('crm') && \CCrmPerms::isAccessEnabled())
551 {
552 $crmAvailable = $USER->isAdmin() || $USER->canDoOperation('bitrix24_config')
553 || \COption::getOptionString('intranet', 'allow_external_mail_crm', 'Y', SITE_ID) == 'Y';
554
555 $mailboxData['OPTIONS']['sync_from'] = strtotime('today UTC 00:00'.sprintf('-%u days', self::MESSAGE_MAX_AGE));
556
557 if ($crmAvailable)
558 {
559 $maxAge = self::CRM_MAX_AGE;
560 $mailboxData['OPTIONS']['flags'][] = 'crm_connect';
561 $mailboxData['OPTIONS']['crm_sync_from'] = strtotime(sprintf('-%u days', $maxAge));
562 $mailboxData['OPTIONS']['crm_new_entity_in'] = \CCrmOwnerType::LeadName;
563 $mailboxData['OPTIONS']['crm_new_entity_out'] = \CCrmOwnerType::ContactName;
564 $mailboxData['OPTIONS']['crm_lead_source'] = 'EMAIL';
565 $mailboxData['OPTIONS']['crm_lead_resp'] = [empty($mailbox) ? $USER->getId() : $mailbox['USER_ID']];
566 }
567 }
568
569 if (!empty($senderFields) && empty($senderFields['IS_CONFIRMED']))
570 {
571 $result = $this->appendSender($senderFields, (string)($fields['user_principal_name'] ?? ''));
572
573 if (!empty($result['errors']) && $result['errors'] instanceof Main\ErrorCollection)
574 {
575 $this->addErrors($result['errors'], $isOAuth, true);
576 return [];
577 }
578 else if (!empty($result['error']))
579 {
580 $this->addError($result['error']);
581 return [];
582 }
583 else if (empty($result['confirmed']))
584 {
585 $this->addError('MAIL_CLIENT_CONFIG_SMTP_CONFIRM');
586 return [];
587 }
588 }
589
590 $mailboxData['OPTIONS']['version'] = 6;
591
592 if (empty($mailbox))
593 {
594 $mailboxData = array_merge([
595 'LID' => $currentSite['LID'],
596 'ACTIVE' => 'Y',
597 'SERVICE_ID' => $service['ID'],
598 'SERVER_TYPE' => $service['SERVICE_TYPE'],
599 'CHARSET' => $currentSite['CHARSET'],
600 'USER_ID' => $USER->getId(),
601 'SYNC_LOCK' => time()
602 ], $mailboxData);
603
604 $result = $mailboxId = \CMailbox::add($mailboxData);
605
606 addEventToStatFile('mail', 'add_mailbox', $service['NAME'], ($result > 0 ? 'success' : 'failed'));
607 }
608 else
609 {
610 $this->setError(self::EXISTS_ERROR_KEY);
611 return [];
612 }
613
614 if (!($result > 0))
615 {
616 $this->setError();
617 return [];
618 }
619
620 $ownerAccessCode = 'U' . (empty($mailbox) ? $USER->getId() : $mailbox['USER_ID']);
621 $access = array($ownerAccessCode);
622
623 foreach (array_unique($access) as $item)
624 {
625 Mail\Internals\MailboxAccessTable::add(array(
626 'MAILBOX_ID' => $mailboxId,
627 'TASK_ID' => 0,
628 'ACCESS_CODE' => $item,
629 ));
630 }
631
632 $mailboxHelper = Mailbox::createInstance($mailboxId);
633 $mailboxHelper->cacheDirs();
634
635 $filterFields = [
636 'MAILBOX_ID' => $mailboxId,
637 'NAME' => sprintf('CRM IMAP %u', $mailboxId),
638 'ACTION_TYPE' => 'crm_imap',
639 'WHEN_MAIL_RECEIVED' => 'Y',
640 'WHEN_MANUALLY_RUN' => 'Y',
641 ];
642
643 \CMailFilter::add($filterFields);
644
645 $this->setSuccess();
646
647 if ($syncAfterConnection)
648 {
649 $this->syncMailbox($mailboxId);
650 }
651
652 return [
653 'id' => $mailboxId,
654 'email' => trim($mailboxData['EMAIL']),
655 ];
656 }
657}
connectMailbox(string $login='', string $password='', int $serviceId=0, string $server='', int $port=993, bool $ssl=true, string $storageOauthUid='', bool $syncAfterConnection=true, bool $useSmtp=true, string $serverSmtp='', int $portSmtp=587, bool $sslSmtp=true, string $loginSmtp='', string $passwordSMTP='',)
addErrors(Main\ErrorCollection $errors, bool $isOAuth=false, bool $isSender=false)
static appendSender(array $senderFields, string $userPrincipalName)
static createInstance($id, $throw=true)
Definition mailbox.php:101
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29
static isModuleInstalled($moduleName)