diff --git a/application/Espo/Controllers/PopupNotification.php b/application/Espo/Controllers/PopupNotification.php index b8c05ac466..f02e18d2b8 100644 --- a/application/Espo/Controllers/PopupNotification.php +++ b/application/Espo/Controllers/PopupNotification.php @@ -35,7 +35,7 @@ use stdClass; class PopupNotification { - private $service; + private Service $service; public function __construct(Service $service) { @@ -44,6 +44,23 @@ class PopupNotification public function getActionGrouped(): stdClass { - return $this->service->getGroupedList(); + $grouped = $this->service->getGrouped(); + + $result = (object) []; + + foreach ($grouped as $type => $itemList) { + $rawList = array_map( + function ($item) { + return (object) [ + 'id' => $item->getId(), + 'data' => $item->getData(), + ]; + }, + $itemList + ); + $result->$type = $rawList; + } + + return $result; } } diff --git a/application/Espo/Modules/Crm/Controllers/Activities.php b/application/Espo/Modules/Crm/Controllers/Activities.php index 918fd6b2af..6871f03221 100644 --- a/application/Espo/Modules/Crm/Controllers/Activities.php +++ b/application/Espo/Modules/Crm/Controllers/Activities.php @@ -40,7 +40,7 @@ use Espo\Core\Field\DateTime; use Espo\Core\Record\SearchParamsFetcher; use Espo\Modules\Crm\Tools\Calendar\FetchParams; -use Espo\Modules\Crm\Services\Activities as Service; +use Espo\Modules\Crm\Tools\Activities\Service as Service; use Espo\Modules\Crm\Tools\Calendar\Item as CalendarItem; use Espo\Modules\Crm\Tools\Calendar\Service as CalendarService; use Espo\Entities\User; @@ -108,8 +108,8 @@ class Activities $fetchParams = FetchParams ::create( - DateTime::fromString($from . ':00'), - DateTime::fromString($to . ':00') + DateTime::fromString($from), + DateTime::fromString($to) ) ->withScopeList($scopeList); @@ -244,17 +244,6 @@ class Activities ); } - /** - * @return array> - * @throws \Exception - */ - public function getActionPopupNotifications(): array - { - $userId = $this->user->getId(); - - return $this->service->getPopupNotifications($userId); - } - /** * @throws BadRequest */ @@ -404,8 +393,8 @@ class Activities $map = $this->calendarService->fetchBusyRangesForUsers( $userIdList, - DateTime::fromString($from . ':00'), - DateTime::fromString($to . ':00'), + DateTime::fromString($from), + DateTime::fromString($to), $request->getQueryParam('entityType'), $request->getQueryParam('entityId') ); diff --git a/application/Espo/Modules/Crm/Entities/Reminder.php b/application/Espo/Modules/Crm/Entities/Reminder.php index bd9aa9ee59..3eb7ab8b33 100644 --- a/application/Espo/Modules/Crm/Entities/Reminder.php +++ b/application/Espo/Modules/Crm/Entities/Reminder.php @@ -33,6 +33,9 @@ class Reminder extends \Espo\Core\ORM\Entity { public const ENTITY_TYPE = 'Reminder'; + public const TYPE_POPUP = 'Popup'; + public const TYPE_EMAIL = 'Email'; + public function getUserId(): ?string { return $this->get('userId'); diff --git a/application/Espo/Modules/Crm/Resources/metadata/app/popupNotifications.json b/application/Espo/Modules/Crm/Resources/metadata/app/popupNotifications.json index 32b96ce4c1..25abf43ef5 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/app/popupNotifications.json +++ b/application/Espo/Modules/Crm/Resources/metadata/app/popupNotifications.json @@ -1,7 +1,7 @@ { "event": { - "url": "Activities/action/popupNotifications", "grouped": true, + "providerClassName": "Espo\\Modules\\Crm\\Tools\\Activities\\PopupNotificationsProvider", "serviceName": "Activities", "methodName": "getPopupNotifications", "interval": 15, diff --git a/application/Espo/Modules/Crm/Tools/Activities/PopupNotificationsProvider.php b/application/Espo/Modules/Crm/Tools/Activities/PopupNotificationsProvider.php new file mode 100644 index 0000000000..cf16889cf4 --- /dev/null +++ b/application/Espo/Modules/Crm/Tools/Activities/PopupNotificationsProvider.php @@ -0,0 +1,157 @@ +config = $config; + $this->entityManager = $entityManager; + } + + /** + * @return Item[] + * @throws Exception + */ + public function get(User $user): array + { + $userId = $user->getId(); + + $dt = new DateTime(); + + $pastHours = $this->config->get('reminderPastHours', self::REMINDER_PAST_HOURS); + + $now = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); + + $nowShifted = $dt + ->sub(new DateInterval('PT' . $pastHours . 'H')) + ->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); + + /** @var iterable $reminderCollection */ + $reminderCollection = $this->entityManager + ->getRDBRepositoryByClass(Reminder::class) + ->select([ + 'id', + 'entityType', + 'entityId', + ]) + ->where([ + 'type' => Reminder::TYPE_POPUP, + 'userId' => $userId, + 'remindAt<=' => $now, + 'startAt>' => $nowShifted, + ]) + ->find(); + + $resultList = []; + + foreach ($reminderCollection as $reminder) { + $reminderId = $reminder->getId(); + $entityType = $reminder->getTargetEntityType(); + $entityId = $reminder->getTargetEntityId(); + + if (!$entityId || !$entityType) { + continue; + } + + $entity = $this->entityManager->getEntityById($entityType, $entityId); + + if (!$entity) { + continue; + } + + $data = null; + + if ( + $entity instanceof CoreEntity && + $entity->hasLinkMultipleField('users') + ) { + $entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']); + + $status = $entity->getLinkMultipleColumn('users', 'status', $userId); + + if ($status === Meeting::ATTENDEE_STATUS_DECLINED) { + $this->removeReminder($reminderId); + + continue; + } + } + + $dateAttribute = $entityType === Task::ENTITY_TYPE ? + 'dateEnd' : + 'dateStart'; + + $data = (object) [ + 'id' => $entity->getId(), + 'entityType' => $entityType, + $dateAttribute => $entity->get($dateAttribute), + 'name' => $entity->get('name'), + ]; + + $resultList[] = new Item($reminderId, $data); + } + + return $resultList; + } + + private function removeReminder(string $id): void + { + $deleteQuery = $this->entityManager + ->getQueryBuilder() + ->delete() + ->from(Reminder::ENTITY_TYPE) + ->where(['id' => $id]) + ->build(); + + $this->entityManager->getQueryExecutor()->execute($deleteQuery); + } +} diff --git a/application/Espo/Modules/Crm/Services/Activities.php b/application/Espo/Modules/Crm/Tools/Activities/Service.php similarity index 88% rename from application/Espo/Modules/Crm/Services/Activities.php rename to application/Espo/Modules/Crm/Tools/Activities/Service.php index 5d4d4c2601..47d783b4d8 100644 --- a/application/Espo/Modules/Crm/Services/Activities.php +++ b/application/Espo/Modules/Crm/Tools/Activities/Service.php @@ -27,12 +27,28 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Modules\Crm\Services; +namespace Espo\Modules\Crm\Tools\Activities; +use Espo\Core\Acl; use Espo\Core\Exceptions\Error; use Espo\Core\Exceptions\NotFound; use Espo\Core\Exceptions\Forbidden; +use Espo\Core\ServiceFactory; +use Espo\Core\Utils\Config; +use Espo\Core\Utils\DateTime as DateTimeUtil; +use Espo\Core\Utils\Metadata; +use Espo\Entities\Email; +use Espo\Entities\Preferences; +use Espo\Entities\User; +use Espo\Modules\Crm\Entities\Account; +use Espo\Modules\Crm\Entities\Call; +use Espo\Modules\Crm\Entities\Contact; +use Espo\Modules\Crm\Entities\Lead; +use Espo\Modules\Crm\Entities\Meeting; +use Espo\Modules\Crm\Entities\Reminder; +use Espo\Modules\Crm\Entities\Task; +use Espo\ORM\EntityManager; use Espo\ORM\Query\UnionBuilder; use Espo\ORM\Query\SelectBuilder; @@ -48,74 +64,77 @@ use Espo\Core\Select\Where\ConverterFactory as WhereConverterFactory; use Espo\Core\Select\SelectBuilderFactory; use Espo\Core\FieldProcessing\ListLoadProcessor; use Espo\Core\FieldProcessing\Loader\Params as FieldLoaderParams; -use Espo\Core\Di; use Espo\Core\Record\ServiceContainer as RecordServiceContainer; -use Espo\Core\ORM\Entity as CoreEntity; - -use Espo\Entities\User as UserEntity; use PDO; -use Exception; use DateTime; -use DateInterval; use stdClass; -class Activities implements - - Di\ConfigAware, - Di\MetadataAware, - Di\AclAware, - Di\ServiceFactoryAware, - Di\EntityManagerAware, - Di\UserAware +class Service { - use Di\ConfigSetter; - use Di\MetadataSetter; - use Di\AclSetter; - use Di\ServiceFactorySetter; - use Di\EntityManagerSetter; - use Di\UserSetter; - - const UPCOMING_ACTIVITIES_FUTURE_DAYS = 1; - const UPCOMING_ACTIVITIES_TASK_FUTURE_DAYS = 7; - const REMINDER_PAST_HOURS = 24; + private const UPCOMING_ACTIVITIES_FUTURE_DAYS = 1; + private const UPCOMING_ACTIVITIES_TASK_FUTURE_DAYS = 7; private WhereConverterFactory $whereConverterFactory; private ListLoadProcessor $listLoadProcessor; private RecordServiceContainer $recordServiceContainer; private SelectBuilderFactory $selectBuilderFactory; + private Config $config; + private Metadata $metadata; + private Acl $acl; + private ServiceFactory $serviceFactory; + private EntityManager $entityManager; + private User $user; public function __construct( WhereConverterFactory $whereConverterFactory, ListLoadProcessor $listLoadProcessor, RecordServiceContainer $recordServiceContainer, - SelectBuilderFactory $selectBuilderFactory + SelectBuilderFactory $selectBuilderFactory, + Config $config, + Metadata $metadata, + Acl $acl, + ServiceFactory $serviceFactory, + EntityManager $entityManager, + User $user ) { $this->whereConverterFactory = $whereConverterFactory; $this->listLoadProcessor = $listLoadProcessor; $this->recordServiceContainer = $recordServiceContainer; $this->selectBuilderFactory = $selectBuilderFactory; + $this->config = $config; + $this->metadata = $metadata; + $this->acl = $acl; + $this->serviceFactory = $serviceFactory; + $this->entityManager = $entityManager; + $this->user = $user; } protected function isPerson(string $scope): bool { - return in_array($scope, ['Contact', 'Lead', 'User']) || + return + in_array( + $scope, + [Contact::ENTITY_TYPE, Lead::ENTITY_TYPE, User::ENTITY_TYPE] + ) || $this->metadata->get(['scopes', $scope, 'type']) === 'Person'; } protected function isCompany(string $scope): bool { - return in_array($scope, ['Account']) || $this->metadata->get(['scopes', $scope, 'type']) === 'Company'; + return + in_array($scope, [Account::ENTITY_TYPE]) || + $this->metadata->get(['scopes', $scope, 'type']) === 'Company'; } /** * @param string[] $statusList */ - protected function getActivitiesUserMeetingQuery(UserEntity $entity, array $statusList = []): Select + protected function getActivitiesUserMeetingQuery(User $entity, array $statusList = []): Select { $builder = $this->selectBuilderFactory ->create() - ->from('Meeting') + ->from(Meeting::ENTITY_TYPE) ->withStrictAccessControl() ->buildQueryBuilder() ->select([ @@ -172,13 +191,13 @@ class Activities implements /** * @param string[] $statusList */ - protected function getActivitiesUserCallQuery(UserEntity $entity, array $statusList = []): Select + protected function getActivitiesUserCallQuery(User $entity, array $statusList = []): Select { - $seed = $this->entityManager->getNewEntity('Call'); + $seed = $this->entityManager->getNewEntity(Call::ENTITY_TYPE); $builder = $this->selectBuilderFactory ->create() - ->from('Call') + ->from(Call::ENTITY_TYPE) ->withStrictAccessControl() ->buildQueryBuilder() ->select([ @@ -242,10 +261,10 @@ class Activities implements * @param string[] $statusList * @return Select|Select[] */ - protected function getActivitiesUserEmailQuery(UserEntity $entity, array $statusList = []) + protected function getActivitiesUserEmailQuery(User $entity, array $statusList = []) { if ($entity->isPortal() && $entity->get('contactId')) { - $contact = $this->entityManager->getEntity('Contact', $entity->get('contactId')); + $contact = $this->entityManager->getEntity(Contact::ENTITY_TYPE, $entity->get('contactId')); if ($contact) { return $this->getActivitiesEmailQuery($contact, $statusList); @@ -254,7 +273,7 @@ class Activities implements $builder = $this->selectBuilderFactory ->create() - ->from('Email') + ->from(Email::ENTITY_TYPE) ->withStrictAccessControl() ->buildQueryBuilder() ->select([ @@ -350,12 +369,12 @@ class Activities implements $builder = clone $baseBuilder; - if ($entityType == 'Account') { + if ($entityType === Account::ENTITY_TYPE) { $builder->where([ 'OR' => [ [ 'parentId' => $id, - 'parentType' => 'Account', + 'parentType' => Account::ENTITY_TYPE, ], [ 'accountId' => $id, @@ -363,12 +382,12 @@ class Activities implements ], ]); } - else if ($entityType == 'Lead' && $entity->get('createdAccountId')) { + else if ($entityType === Lead::ENTITY_TYPE && $entity->get('createdAccountId')) { $builder->where([ 'OR' => [ [ 'parentId' => $id, - 'parentType' => 'Lead', + 'parentType' => Lead::ENTITY_TYPE, ], [ 'accountId' => $entity->get('createdAccountId'), @@ -392,17 +411,17 @@ class Activities implements $link = null; switch ($entityType) { - case 'Contact': + case Contact::ENTITY_TYPE: $link = 'contacts'; break; - case 'Lead': + case Lead::ENTITY_TYPE: $link = 'leads'; break; - case 'User': + case User::ENTITY_TYPE: $link = 'users'; break; @@ -437,7 +456,7 @@ class Activities implements */ protected function getActivitiesMeetingQuery(Entity $entity, array $statusList = []) { - return $this->getActivitiesMeetingOrCallQuery($entity, $statusList, 'Meeting'); + return $this->getActivitiesMeetingOrCallQuery($entity, $statusList, Meeting::ENTITY_TYPE); } /** @@ -446,7 +465,7 @@ class Activities implements */ protected function getActivitiesCallQuery(Entity $entity, array $statusList = []) { - return $this->getActivitiesMeetingOrCallQuery($entity, $statusList, 'Call'); + return $this->getActivitiesMeetingOrCallQuery($entity, $statusList, Call::ENTITY_TYPE); } /** @@ -494,12 +513,12 @@ class Activities implements $builder = clone $baseBuilder; - if ($entityType == 'Account') { + if ($entityType === Account::ENTITY_TYPE) { $builder->where([ 'OR' => [ [ 'parentId' => $id, - 'parentType' => 'Account', + 'parentType' => Account::ENTITY_TYPE, ], [ 'accountId' => $id, @@ -512,7 +531,7 @@ class Activities implements 'OR' => [ [ 'parentId' => $id, - 'parentType' => 'Lead', + 'parentType' => Lead::ENTITY_TYPE, ], [ 'accountId' => $entity->get('createdAccountId'), @@ -646,7 +665,7 @@ class Activities implements $offset = $params['offset'] ?? 0; - if (!$onlyScope && $scope === 'User') { + if (!$onlyScope && $scope === User::ENTITY_TYPE) { // optimizing sub-queries $newQueryList = []; @@ -733,7 +752,7 @@ class Activities implements $list[] = $row; } - if ($scope === 'User') { + if ($scope === User::ENTITY_TYPE) { if ($maxSize && count($list) > $maxSize) { $totalCount = -1; @@ -752,7 +771,7 @@ class Activities implements */ protected function accessCheck(Entity $entity): void { - if ($entity instanceof UserEntity) { + if ($entity instanceof User) { if (!$this->acl->checkUserPermission($entity, 'user')) { throw new Forbidden(); } @@ -796,13 +815,15 @@ class Activities implements } if (!$isHistory) { - $statusList = $this->metadata->get(['scopes', $entityType, 'activityStatusList'], ['Planned']); + $statusList = $this->metadata->get(['scopes', $entityType, 'activityStatusList']) ?? + [Meeting::STATUS_PLANNED]; } else { - $statusList = $this->metadata->get(['scopes', $entityType, 'historyStatusList'], ['Held', 'Not Held']); + $statusList = $this->metadata->get(['scopes', $entityType, 'historyStatusList']) ?? + [Meeting::STATUS_HELD, Meeting::STATUS_NOT_HELD]; } - if ($entityType === 'Email' && $searchParams->getOrderBy() === 'dateStart') { + if ($entityType === Email::ENTITY_TYPE && $searchParams->getOrderBy() === 'dateStart') { $searchParams = $searchParams->withOrderBy('dateSent'); } @@ -947,7 +968,8 @@ class Activities implements $parts = []; /** @var string[] $entityTypeList */ - $entityTypeList = $this->config->get('activitiesEntityList', ['Meeting', 'Call']); + $entityTypeList = $this->config->get('activitiesEntityList') ?? + [Meeting::ENTITY_TYPE, Call::ENTITY_TYPE]; foreach ($entityTypeList as $entityType) { if (!$fetchAll && $targetScope !== $entityType) { @@ -962,7 +984,8 @@ class Activities implements continue; } - $statusList = $this->metadata->get(['scopes', $entityType, 'activityStatusList'], ['Planned']); + $statusList = $this->metadata->get(['scopes', $entityType, 'activityStatusList']) ?? + [Meeting::STATUS_PLANNED]; $parts[$entityType] = $this->getActivitiesQuery($entity, $entityType, $statusList); } @@ -1006,7 +1029,12 @@ class Activities implements $parts = []; /** @var string[] $entityTypeList */ - $entityTypeList = $this->config->get('historyEntityList', ['Meeting', 'Call', 'Email']); + $entityTypeList = $this->config->get('historyEntityList') ?? + [ + Meeting::ENTITY_TYPE, + Call::ENTITY_TYPE, + Email::ENTITY_TYPE + ]; foreach ($entityTypeList as $entityType) { if (!$fetchAll && $targetScope !== $entityType) { @@ -1021,9 +1049,8 @@ class Activities implements continue; } - $statusList = $this->metadata->get( - ['scopes', $entityType, 'historyStatusList'], ['Held', 'Not Held'] - ); + $statusList = $this->metadata->get(['scopes', $entityType, 'historyStatusList']) ?? + [Meeting::STATUS_HELD, Meeting::STATUS_NOT_HELD]; $parts[$entityType] = $this->getActivitiesQuery($entity, $entityType, $statusList); } @@ -1031,7 +1058,7 @@ class Activities implements $result = $this->getResultFromQueryParts($parts, $scope, $params); foreach ($result['list'] as &$item) { - if ($item['_scope'] == 'Email') { + if ($item['_scope'] === Email::ENTITY_TYPE) { $item['dateSent'] = $item['dateStart']; } } @@ -1099,7 +1126,7 @@ class Activities implements ['""', 'hasAttachment'], ]); - if ($entity->getEntityType() === 'User') { + if ($entity->getEntityType() === User::ENTITY_TYPE) { $builder->where([ 'assignedUserId' => $entity->getId(), ]); @@ -1123,7 +1150,7 @@ class Activities implements $builder = $this->entityManager ->getQueryBuilder() ->delete() - ->from('Reminder') + ->from(Reminder::ENTITY_TYPE) ->where([ 'id' => $id, ]); @@ -1139,82 +1166,6 @@ class Activities implements $this->entityManager->getQueryExecutor()->execute($deleteQuery); } - /** - * @return array> - * @throws Exception - */ - public function getPopupNotifications(string $userId): array - { - $dt = new DateTime(); - - $pastHours = $this->config->get('reminderPastHours', self::REMINDER_PAST_HOURS); - - $now = $dt->format('Y-m-d H:i:s'); - $nowShifted = $dt->sub(new DateInterval('PT'.strval($pastHours).'H'))->format('Y-m-d H:i:s'); - - /** @var iterable<\Espo\Modules\Crm\Entities\Reminder> $reminderCollection */ - $reminderCollection = $this->entityManager - ->getRDBRepository('Reminder') - ->select(['id', 'entityType', 'entityId']) - ->where([ - 'type' => 'Popup', - 'userId' => $userId, - 'remindAt<=' => $now, - 'startAt>' => $nowShifted, - ]) - ->find(); - - $resultList = []; - - foreach ($reminderCollection as $reminder) { - $reminderId = $reminder->get('id'); - $entityType = $reminder->get('entityType'); - $entityId = $reminder->get('entityId'); - - $entity = $this->entityManager->getEntity($entityType, $entityId); - - $data = null; - - if (!$entity) { - continue; - } - - if ( - $entity instanceof CoreEntity && - $entity->hasLinkMultipleField('users') - ) { - $entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']); - $status = $entity->getLinkMultipleColumn('users', 'status', $userId); - - if ($status === 'Declined') { - $this->removeReminder($reminderId); - - continue; - } - } - - $dateAttribute = 'dateStart'; - - if ($entityType === 'Task') { - $dateAttribute = 'dateEnd'; - } - - $data = [ - 'id' => $entity->getId(), - 'entityType' => $entityType, - $dateAttribute => $entity->get($dateAttribute), - 'name' => $entity->get('name'), - ]; - - $resultList[] = [ - 'id' => $reminderId, - 'data' => $data, - ]; - } - - return $resultList; - } - /** * @param array{ * offset?: ?int, @@ -1235,7 +1186,7 @@ class Activities implements ?int $futureDays = null ): array { - $user = $this->entityManager->getEntity('User', $userId); + $user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $userId); if (!$user) { throw new NotFound(); @@ -1259,7 +1210,7 @@ class Activities implements foreach ($entityTypeList as $entityType) { if ( !$this->metadata->get(['scopes', $entityType, 'activity']) && - $entityType !== 'Task' + $entityType !== Task::ENTITY_TYPE ) { continue; } @@ -1351,13 +1302,13 @@ class Activities implements protected function getUpcomingActivitiesEntityTypeQuery( string $entityType, array $params, - UserEntity $user, + User $user, int $futureDays ): Select { $beforeString = (new DateTime()) ->modify('+' . $futureDays . ' days') - ->format('Y-m-d H:i:s'); + ->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); $builder = $this->selectBuilderFactory ->create() @@ -1368,7 +1319,7 @@ class Activities implements $primaryFilter = 'planned'; - if ($entityType === 'Task') { + if ($entityType === Task::ENTITY_TYPE) { $primaryFilter = 'actual'; } @@ -1384,7 +1335,7 @@ class Activities implements $timeZone = $this->getUserTimeZone($user); - if ($entityType === 'Task') { + if ($entityType === Task::ENTITY_TYPE) { $upcomingTaskFutureDays = $this->config->get( 'activitiesUpcomingTaskFutureDays', self::UPCOMING_ACTIVITIES_TASK_FUTURE_DAYS @@ -1392,7 +1343,7 @@ class Activities implements $taskBeforeString = (new DateTime()) ->modify('+' . $upcomingTaskFutureDays . ' days') - ->format('Y-m-d H:i:s'); + ->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); $queryBuilder->where([ 'OR' => [ @@ -1489,9 +1440,9 @@ class Activities implements return $queryBuilder->build(); } - protected function getUserTimeZone(UserEntity $user): string + protected function getUserTimeZone(User $user): string { - $preferences = $this->entityManager->getEntity('Preferences', $user->getId()); + $preferences = $this->entityManager->getEntityById(Preferences::ENTITY_TYPE, $user->getId()); if ($preferences) { $timeZone = $preferences->get('timeZone'); diff --git a/application/Espo/Modules/Crm/Tools/Calendar/Service.php b/application/Espo/Modules/Crm/Tools/Calendar/Service.php index b74ebc1574..c3fb029d36 100644 --- a/application/Espo/Modules/Crm/Tools/Calendar/Service.php +++ b/application/Espo/Modules/Crm/Tools/Calendar/Service.php @@ -57,8 +57,6 @@ use Espo\Core\ServiceFactory; use Espo\Tools\WorkingTime\Extractor; use PDO; use Exception; -use stdClass; -use DateTime; class Service { diff --git a/application/Espo/Tools/PopupNotification/Item.php b/application/Espo/Tools/PopupNotification/Item.php new file mode 100644 index 0000000000..67b40e62de --- /dev/null +++ b/application/Espo/Tools/PopupNotification/Item.php @@ -0,0 +1,60 @@ +id = $id; + $this->data = $data; + } + + public function getId(): ?string + { + return $this->id; + } + + public function getData(): stdClass + { + return $this->data; + } +} diff --git a/application/Espo/Tools/PopupNotification/Provider.php b/application/Espo/Tools/PopupNotification/Provider.php new file mode 100644 index 0000000000..81121b1065 --- /dev/null +++ b/application/Espo/Tools/PopupNotification/Provider.php @@ -0,0 +1,43 @@ +metadata = $metadata; $this->serviceFactory = $serviceFactory; $this->user = $user; $this->log = $log; + $this->injectableFactory = $injectableFactory; } - public function getGroupedList(): stdClass + /** + * @return array Items grouped by type. + */ + public function getGrouped(): array { $data = $this->metadata->get(['app', 'popupNotifications']) ?? []; @@ -83,16 +90,40 @@ class Service return true; }); - $result = (object) []; + $result = []; foreach ($data as $type => $item) { - $serviceName = $item['serviceName']; - $methodName = $item['methodName']; + /** @var ?class-string $className */ + $className = $item['providerClassName'] ?? null; try { + if ($className) { + $provider = $this->injectableFactory->create($className); + + $result[$type] = $provider->get($this->user); + + continue; + } + + // For bc. + + $serviceName = $item['serviceName']; + $methodName = $item['methodName']; + $service = $this->serviceFactory->create($serviceName); - $result->$type = $service->$methodName($this->user->id); + $itemList = array_map( + function ($raw) { + if ($raw instanceof stdClass) { + return new Item($raw->id ?? null, $raw->data); + } + + return new Item($raw['id'] ?? null, $raw['data']); + }, + $service->$methodName($this->user->getId()) + ); + + $result[$type] = $itemList; } catch (Throwable $e) { $this->log->error("Popup notifications: " . $e->getMessage());