diff --git a/application/Espo/Core/WebSocket/ConfigDataProvider.php b/application/Espo/Core/WebSocket/ConfigDataProvider.php new file mode 100644 index 0000000000..c662c26107 --- /dev/null +++ b/application/Espo/Core/WebSocket/ConfigDataProvider.php @@ -0,0 +1,98 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\WebSocket; + +use Espo\Core\Utils\Config; + +/** + * @since 9.1.0 + */ +class ConfigDataProvider +{ + public function __construct( + private Config $config, + ) {} + + public function isEnabled(): bool + { + return (bool) $this->config->get('useWebSocket'); + } + + public function isDebugMode(): bool + { + return (bool) $this->config->get('webSocketDebugMode'); + } + + public function useSecureServer(): bool + { + return (bool) $this->config->get('webSocketUseSecureServer'); + } + + public function getPort(): ?string + { + $port = $this->config->get('webSocketPort'); + + if (!$port) { + return null; + } + + return (string) $port; + } + + public function getPhpExecutablePath(): ?string + { + return $this->config->get('phpExecutablePath'); + } + + public function getSslCertificateFile(): ?string + { + return $this->config->get('webSocketSslCertificateFile'); + } + + public function allowSelfSignedSsl(): bool + { + return (bool) $this->config->get('webSocketSslAllowSelfSigned'); + } + + public function getSslCertificatePassphrase(): ?string + { + return $this->config->get('webSocketSslCertificatePassphrase'); + } + + public function getSslCertificateLocalPrivateKey(): ?string + { + return $this->config->get('webSocketSslCertificateLocalPrivateKey'); + } + + public function getMessager(): ?string + { + return $this->config->get('webSocketMessager'); + } +} diff --git a/application/Espo/Core/WebSocket/SenderFactory.php b/application/Espo/Core/WebSocket/SenderFactory.php index c6e811bee8..348fd019ca 100644 --- a/application/Espo/Core/WebSocket/SenderFactory.php +++ b/application/Espo/Core/WebSocket/SenderFactory.php @@ -30,7 +30,6 @@ namespace Espo\Core\WebSocket; use Espo\Core\InjectableFactory; -use Espo\Core\Utils\Config; use Espo\Core\Utils\Metadata; use Espo\Core\Binding\Factory; @@ -45,19 +44,19 @@ class SenderFactory implements Factory public function __construct( private InjectableFactory $injectableFactory, - private Config $config, - private Metadata $metadata + private ConfigDataProvider $config, + private Metadata $metadata, ) {} public function create(): Sender { - $messager = $this->config->get('webSocketMessager') ?? self::DEFAULT_MESSAGER; + $messager = $this->config->getMessager() ?? self::DEFAULT_MESSAGER; /** @var ?class-string $className */ $className = $this->metadata->get(['app', 'webSocket', 'messagers', $messager, 'senderClassName']); if (!$className) { - throw new RuntimeException("No sender for messager '{$messager}'."); + throw new RuntimeException("No sender for messager '$messager'."); } return $this->injectableFactory->create($className); diff --git a/application/Espo/Core/WebSocket/ServerStarter.php b/application/Espo/Core/WebSocket/ServerStarter.php index 63e3470cc9..72e92f0fda 100644 --- a/application/Espo/Core/WebSocket/ServerStarter.php +++ b/application/Espo/Core/WebSocket/ServerStarter.php @@ -29,7 +29,6 @@ namespace Espo\Core\WebSocket; -use Espo\Core\Utils\Config; use Espo\Core\Utils\Metadata; use React\EventLoop\Factory as EventLoopFactory; @@ -55,15 +54,15 @@ class ServerStarter public function __construct( private Subscriber $subscriber, - private Config $config, + private ConfigDataProvider $configDataProvider, Metadata $metadata ) { $this->categoriesData = $metadata->get(['app', 'webSocket', 'categories'], []); - $this->phpExecutablePath = $config->get('phpExecutablePath'); - $this->isDebugMode = (bool) $config->get('webSocketDebugMode'); - $this->useSecureServer = (bool) $config->get('webSocketUseSecureServer'); - $port = $this->config->get('webSocketPort'); + $this->phpExecutablePath = $this->configDataProvider->getPhpExecutablePath(); + $this->isDebugMode = $this->configDataProvider->isDebugMode(); + $this->useSecureServer = $this->configDataProvider->useSecureServer(); + $port = $this->configDataProvider->getPort(); if (!$port) { $port = $this->useSecureServer ? '8443' : '8080'; @@ -105,20 +104,20 @@ class ServerStarter /** * @return array */ - protected function getSslParams(): array + private function getSslParams(): array { $sslParams = [ - 'local_cert' => $this->config->get('webSocketSslCertificateFile'), - 'allow_self_signed' => $this->config->get('webSocketSslAllowSelfSigned', false), + 'local_cert' => $this->configDataProvider->getSslCertificateFile(), + 'allow_self_signed' => $this->configDataProvider->allowSelfSignedSsl(), 'verify_peer' => false, ]; - if ($this->config->get('webSocketSslCertificatePassphrase')) { - $sslParams['passphrase'] = $this->config->get('webSocketSslCertificatePassphrase'); + if ($this->configDataProvider->getSslCertificatePassphrase()) { + $sslParams['passphrase'] = $this->configDataProvider->getSslCertificatePassphrase(); } - if ($this->config->get('webSocketSslCertificateLocalPrivateKey')) { - $sslParams['local_pk'] = $this->config->get('webSocketSslCertificateLocalPrivateKey'); + if ($this->configDataProvider->getSslCertificateLocalPrivateKey()) { + $sslParams['local_pk'] = $this->configDataProvider->getSslCertificateLocalPrivateKey(); } return $sslParams; diff --git a/application/Espo/Core/WebSocket/Submission.php b/application/Espo/Core/WebSocket/Submission.php index cc7234dc6c..9aeca049f4 100644 --- a/application/Espo/Core/WebSocket/Submission.php +++ b/application/Espo/Core/WebSocket/Submission.php @@ -35,25 +35,35 @@ use Espo\Core\Utils\Json; use stdClass; use Throwable; -/** - * @todo Introduce a wrapper class that will skip sending if WebSocket is not enabled. - */ class Submission { public function __construct( private Sender $sender, - private Log $log + private Log $log, + private ConfigDataProvider $configDataProvider, ) {} /** * Submit to a web-socket server. + * + * Since 9.1.0 performs check whether enabled in the config. + * + * @param stdClass|array|null $data Data to submit. Assoc array is supported since 9.1.0. */ - public function submit(string $topic, ?string $userId = null, ?stdClass $data = null): void + public function submit(string $topic, ?string $userId = null, stdClass|array|null $data = null): void { + if (!$this->configDataProvider->isEnabled()) { + return; + } + if (!$data) { $data = (object) []; } + if (is_array($data)) { + $data = (object) $data; + } + if ($userId) { $data->userId = $userId; } diff --git a/application/Espo/Core/WebSocket/SubscriberFactory.php b/application/Espo/Core/WebSocket/SubscriberFactory.php index 1b5542d682..addb22f260 100644 --- a/application/Espo/Core/WebSocket/SubscriberFactory.php +++ b/application/Espo/Core/WebSocket/SubscriberFactory.php @@ -30,7 +30,6 @@ namespace Espo\Core\WebSocket; use Espo\Core\InjectableFactory; -use Espo\Core\Utils\Config; use Espo\Core\Utils\Metadata; use Espo\Core\Binding\Factory; @@ -45,19 +44,19 @@ class SubscriberFactory implements Factory public function __construct( private InjectableFactory $injectableFactory, - private Config $config, - private Metadata $metadata + private ConfigDataProvider $config, + private Metadata $metadata, ) {} public function create(): Subscriber { - $messager = $this->config->get('webSocketMessager') ?? self::DEFAULT_MESSAGER; + $messager = $this->config->getMessager() ?? self::DEFAULT_MESSAGER; /** @var ?class-string $className */ $className = $this->metadata->get(['app', 'webSocket', 'messagers', $messager, 'subscriberClassName']); if (!$className) { - throw new RuntimeException("No subscriber for messager '{$messager}'."); + throw new RuntimeException("No subscriber for messager '$messager'."); } return $this->injectableFactory->create($className); diff --git a/application/Espo/Hooks/Common/WebSocketSubmit.php b/application/Espo/Hooks/Common/WebSocketSubmit.php index dff3baf189..274854fd5a 100644 --- a/application/Espo/Hooks/Common/WebSocketSubmit.php +++ b/application/Espo/Hooks/Common/WebSocketSubmit.php @@ -29,29 +29,29 @@ namespace Espo\Hooks\Common; +use Espo\Core\Hook\Hook\AfterSave; +use Espo\Core\WebSocket\ConfigDataProvider; use Espo\ORM\Entity; - use Espo\Core\ORM\Repository\Option\SaveOption; -use Espo\Core\Utils\Config; use Espo\Core\Utils\Metadata; use Espo\Core\WebSocket\Submission as WebSocketSubmission; +use Espo\ORM\Repository\Option\SaveOptions; -class WebSocketSubmit +/** + * @implements AfterSave + */ +class WebSocketSubmit implements AfterSave { public static int $order = 20; public function __construct( private Metadata $metadata, private WebSocketSubmission $webSocketSubmission, - private Config $config ) {} - /** - * @param array $options - */ - public function afterSave(Entity $entity, array $options): void + public function afterSave(Entity $entity, SaveOptions $options): void { - if ($options[SaveOption::SILENT] ?? false) { + if ($options->get(SaveOption::SILENT)) { return; } @@ -59,19 +59,15 @@ class WebSocketSubmit return; } - if (!$this->config->get('useWebSocket')) { - return; - } - $scope = $entity->getEntityType(); $id = $entity->getId(); - if (!$this->metadata->get(['scopes', $scope, 'object'])) { + if (!$this->metadata->get("scopes.$scope.object")) { return; } - $topic = "recordUpdate.{$scope}.{$id}"; + $topic = "recordUpdate.$scope.$id"; - $this->webSocketSubmission->submit($topic, null); + $this->webSocketSubmission->submit($topic); } } diff --git a/application/Espo/Hooks/Note/WebSocketSubmit.php b/application/Espo/Hooks/Note/WebSocketSubmit.php index 76730411b2..139e2a557f 100644 --- a/application/Espo/Hooks/Note/WebSocketSubmit.php +++ b/application/Espo/Hooks/Note/WebSocketSubmit.php @@ -32,7 +32,6 @@ namespace Espo\Hooks\Note; use Espo\Core\Hook\Hook\AfterSave; use Espo\Entities\Note; use Espo\ORM\Entity; -use Espo\Core\Utils\Config; use Espo\Core\WebSocket\Submission as WebSocketSubmission; use Espo\ORM\Repository\Option\SaveOptions; @@ -45,15 +44,10 @@ class WebSocketSubmit implements AfterSave public function __construct( private WebSocketSubmission $webSocketSubmission, - private Config $config ) {} public function afterSave(Entity $entity, SaveOptions $options): void { - if (!$this->config->get('useWebSocket')) { - return; - } - $parentId = $entity->getParentId(); $parentType = $entity->getParentType(); diff --git a/application/Espo/Hooks/Notification/WebSocketSubmit.php b/application/Espo/Hooks/Notification/WebSocketSubmit.php index 6f81480874..5e67e5e79f 100644 --- a/application/Espo/Hooks/Notification/WebSocketSubmit.php +++ b/application/Espo/Hooks/Notification/WebSocketSubmit.php @@ -29,29 +29,30 @@ namespace Espo\Hooks\Notification; +use Espo\Core\Hook\Hook\AfterSave; +use Espo\Entities\Notification; use Espo\ORM\Entity; - -use Espo\Core\Utils\Config; use Espo\Core\WebSocket\Submission as WebSocketSubmission; +use Espo\ORM\Repository\Option\SaveOptions; -class WebSocketSubmit +/** + * @implements AfterSave + */ +class WebSocketSubmit implements AfterSave { public static int $order = 20; - public function __construct(private WebSocketSubmission $webSocketSubmission, private Config $config) - {} + public function __construct( + private WebSocketSubmission $webSocketSubmission, + ) {} - public function afterSave(Entity $entity): void + public function afterSave(Entity $entity, SaveOptions $options): void { - if (!$this->config->get('useWebSocket')) { - return; - } - if (!$entity->isNew()) { return; } - $userId = $entity->get('userId'); + $userId = $entity->getUserId(); if (!$userId) { return; diff --git a/application/Espo/Modules/Crm/Jobs/SubmitPopupReminders.php b/application/Espo/Modules/Crm/Jobs/SubmitPopupReminders.php index b5d55c51f6..17ac26f8c1 100644 --- a/application/Espo/Modules/Crm/Jobs/SubmitPopupReminders.php +++ b/application/Espo/Modules/Crm/Jobs/SubmitPopupReminders.php @@ -31,6 +31,7 @@ namespace Espo\Modules\Crm\Jobs; use Espo\Core\Name\Field; use Espo\Core\ORM\Entity as CoreEntity; +use Espo\Core\WebSocket\ConfigDataProvider; use Espo\Modules\Crm\Entities\Meeting; use Espo\Modules\Crm\Entities\Reminder; use Espo\Core\Job\JobDataLess; @@ -43,6 +44,9 @@ use Espo\Core\WebSocket\Submission as WebSocketSubmission; use Throwable; use DateTime; +/** + * @noinspection PhpUnused + */ class SubmitPopupReminders implements JobDataLess { private const REMINDER_PAST_HOURS = 24; @@ -51,12 +55,13 @@ class SubmitPopupReminders implements JobDataLess private EntityManager $entityManager, private Config $config, private WebSocketSubmission $webSocketSubmission, - private Log $log + private Log $log, + private ConfigDataProvider $webSocketConfig, ) {} public function run(): void { - if (!$this->config->get('useWebSocket')) { + if (!$this->webSocketConfig->isEnabled()) { return; } @@ -70,7 +75,7 @@ class SubmitPopupReminders implements JobDataLess ->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); $reminderList = $this->entityManager - ->getRDBRepository(Reminder::ENTITY_TYPE) + ->getRDBRepositoryByClass(Reminder::class) ->where([ 'type' => Reminder::TYPE_POPUP, 'remindAt<=' => $now, @@ -146,13 +151,13 @@ class SubmitPopupReminders implements JobDataLess ];; $reminder->set('isSubmitted', true); + $this->entityManager->saveEntity($reminder); } foreach ($submitData as $userId => $list) { try { - $this->webSocketSubmission - ->submit('popupNotifications.event', $userId, (object) ['list' => $list]); + $this->webSocketSubmission->submit('popupNotifications.event', $userId, ['list' => $list]); } catch (Throwable $e) { $this->log->error('Job SubmitPopupReminders: [' . $e->getCode() . '] ' .$e->getMessage()); } diff --git a/application/Espo/Tools/Email/InboxService.php b/application/Espo/Tools/Email/InboxService.php index 2695e48431..f84b6ef6f3 100644 --- a/application/Espo/Tools/Email/InboxService.php +++ b/application/Espo/Tools/Email/InboxService.php @@ -37,7 +37,6 @@ use Espo\Core\Exceptions\NotFound; use Espo\Core\Name\Field; use Espo\Core\Select\SelectBuilderFactory; use Espo\Core\Select\Where\Item as WhereItem; -use Espo\Core\Utils\Config; use Espo\Core\Utils\Log; use Espo\Core\WebSocket\Submission as WebSocketSubmission; use Espo\Entities\Email; @@ -63,7 +62,6 @@ class InboxService private Log $log, private SelectBuilderFactory $selectBuilderFactory, private WebSocketSubmission $webSocketSubmission, - private Config $config, ) {} /** @@ -487,10 +485,6 @@ class InboxService private function submitNotificationWebSocket(string $userId): void { - if (!$this->config->get('useWebSocket')) { - return; - } - $this->webSocketSubmission->submit('newNotification', $userId); } diff --git a/application/Espo/Tools/Notification/Service.php b/application/Espo/Tools/Notification/Service.php index bf075af9b1..a8af901b1a 100644 --- a/application/Espo/Tools/Notification/Service.php +++ b/application/Espo/Tools/Notification/Service.php @@ -35,12 +35,9 @@ use Espo\Entities\Note; use Espo\Entities\Notification; use Espo\Entities\User; use Espo\Entities\Email; - -use Espo\Core\Utils\Config; use Espo\Core\AclManager; use Espo\Core\WebSocket\Submission; use Espo\Core\Utils\DateTime as DateTimeUtil; - use Espo\Modules\Crm\Entities\CaseObj; use Espo\ORM\EntityManager; use Espo\ORM\Name\Attribute; @@ -49,10 +46,9 @@ class Service { public function __construct( private EntityManager $entityManager, - private Config $config, private AclManager $aclManager, private Submission $webSocketSubmission, - private RecordIdGenerator $idGenerator + private RecordIdGenerator $idGenerator, ) {} public function notifyAboutMentionInPost(string $userId, Note $note): void @@ -146,10 +142,8 @@ class Service $this->entityManager->getMapper()->massInsert($collection); - if ($this->config->get('useWebSocket')) { - foreach ($userIdList as $userId) { - $this->webSocketSubmission->submit('newNotification', $userId); - } + foreach ($userIdList as $userId) { + $this->webSocketSubmission->submit('newNotification', $userId); } } diff --git a/application/Espo/Tools/Stream/MyReactionsService.php b/application/Espo/Tools/Stream/MyReactionsService.php index fb90b677f5..7c861656b9 100644 --- a/application/Espo/Tools/Stream/MyReactionsService.php +++ b/application/Espo/Tools/Stream/MyReactionsService.php @@ -144,12 +144,8 @@ class MyReactionsService private function webSocketSubmit(Note $note): void { - if (!$this->config->get('useWebSocket')) { - return; - } - $topic = "streamUpdate.{$note->getParentType()}.{$note->getParentId()}"; - $this->webSocketSubmission->submit($topic, null, (object) ['noteId' => $note->getId()]); + $this->webSocketSubmission->submit($topic, null, ['noteId' => $note->getId()]); } }