diff --git a/application/Espo/Core/Select/Helpers/UserTimeZoneProvider.php b/application/Espo/Core/Select/Helpers/UserTimeZoneProvider.php new file mode 100644 index 0000000000..6342b79059 --- /dev/null +++ b/application/Espo/Core/Select/Helpers/UserTimeZoneProvider.php @@ -0,0 +1,66 @@ +user = $user; + $this->entityManager = $entityManager; + $this->config = $config; + } + + public function get(): string + { + $preferences = $this->entityManager->getEntity(Preferences::ENTITY_TYPE, $this->user->getId()); + + if (!$preferences) { + return 'UTC'; + } + + if ($preferences->get('timeZone') === null || $preferences->get('timeZone') === '') { + return $this->config->get('timeZone'); + } + + return $preferences->get('timeZone') ?? 'UTC'; + } +} diff --git a/application/Espo/Core/Select/Where/DateTimeItemTransformer.php b/application/Espo/Core/Select/Where/DateTimeItemTransformer.php index 26ce3931ed..5e59f536ee 100644 --- a/application/Espo/Core/Select/Where/DateTimeItemTransformer.php +++ b/application/Espo/Core/Select/Where/DateTimeItemTransformer.php @@ -29,10 +29,9 @@ namespace Espo\Core\Select\Where; -use Espo\{ - Core\Exceptions\Error, - Entities\User, -}; +use Espo\Core\Exceptions\Error; +use Espo\Entities\User; +use Espo\Core\Utils\DateTime as DateTimeUtil; use DateTime; use DateTimeZone; @@ -43,19 +42,16 @@ use DateInterval; */ class DateTimeItemTransformer { - protected $entityType; - protected $user; - public function __construct(string $entityType, User $user) + public function __construct(User $user) { - $this->entityType = $entityType; $this->user = $user; } public function transform(Item $item): Item { - $format = 'Y-m-d H:i:s'; + $format = DateTimeUtil::SYSTEM_DATE_TIME_FORMAT; $type = $item->getType(); $value = $item->getValue(); diff --git a/application/Espo/Core/Select/Where/ItemGeneralConverterFactory.php b/application/Espo/Core/Select/Where/ItemGeneralConverterFactory.php new file mode 100644 index 0000000000..1d9fbde35f --- /dev/null +++ b/application/Espo/Core/Select/Where/ItemGeneralConverterFactory.php @@ -0,0 +1,60 @@ +injectableFactory = $injectableFactory; + } + + public function create(string $entityType, User $user): ItemGeneralConverter + { + $container = BindingContainerBuilder::create() + ->bindInstance(User::class, $user) + ->inContext( + ItemGeneralConverter::class, + function (ContextualBinder $binder) use ($entityType) { + $binder->bindValue('$entityType', $entityType); + } + ) + ->build(); + + return $this->injectableFactory->createWithBinding(ItemGeneralConverter::class, $container); + } +} diff --git a/application/Espo/Entities/User.php b/application/Espo/Entities/User.php index 4caf135968..af0a27840d 100644 --- a/application/Espo/Entities/User.php +++ b/application/Espo/Entities/User.php @@ -35,6 +35,24 @@ class User extends Person { public const ENTITY_TYPE = 'User'; + public const ATTRIBUTE_TYPE = 'type'; + + public const ATTRIBUTE_IS_ACTIVE = 'isActive'; + + public const LINK_ACCOUNTS = 'accounts'; + + public const LINK_CONTACT = 'contact'; + + public const LINK_PORTALS = 'portals'; + + public const LINK_TEAMS = 'teams'; + + public const LINK_DEFAULT_TEAM = 'defaultTeam'; + + public const LINK_ROLES = 'roles'; + + public const LINK_PORTAL_ROLES = 'portalRoles'; + public function isActive(): bool { return (bool) $this->get('isActive'); diff --git a/application/Espo/Modules/Crm/SelectManagers/Account.php b/application/Espo/Modules/Crm/Classes/Select/Account/AccessControlFilters/PortalOnlyAccount.php similarity index 55% rename from application/Espo/Modules/Crm/SelectManagers/Account.php rename to application/Espo/Modules/Crm/Classes/Select/Account/AccessControlFilters/PortalOnlyAccount.php index 96966f7169..e95684a50d 100644 --- a/application/Espo/Modules/Crm/SelectManagers/Account.php +++ b/application/Espo/Modules/Crm/Classes/Select/Account/AccessControlFilters/PortalOnlyAccount.php @@ -27,56 +27,36 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Modules\Crm\SelectManagers; +namespace Espo\Modules\Crm\Classes\Select\Account\AccessControlFilters; -class Account extends \Espo\Core\Select\SelectManager +use Espo\Core\Select\AccessControl\Filter; +use Espo\ORM\Query\SelectBuilder; + +use Espo\Entities\User; + +class PortalOnlyAccount implements Filter { - protected function filterPartners(&$result) + private $user; + + public function __construct(User $user) { - $result['whereClause'][] = array( - 'type' => 'Partner' - ); + $this->user = $user; } - protected function filterCustomers(&$result) + public function apply(SelectBuilder $queryBuilder): void { - $result['whereClause'][] = array( - 'type' => 'Customer' - ); - } + $accountIdList = $this->user->getLinkMultipleIdList(User::LINK_ACCOUNTS); - protected function filterResellers(&$result) - { - $result['whereClause'][] = array( - 'type' => 'Reseller' - ); - } - - protected function filterRecentlyCreated(&$result) - { - $dt = new \DateTime('now'); - $dt->modify('-7 days'); - - $result['whereClause'][] = array( - 'createdAt>=' => $dt->format('Y-m-d H:i:s') - ); - } - - protected function accessPortalOnlyAccount(&$result) - { - $d = array(); - - $accountIdList = $this->getUser()->getLinkMultipleIdList('accounts'); - - if (count($accountIdList)) { - $result['whereClause'][] = array( - 'id' => $accountIdList - ); - } else { - $result['whereClause'][] = array( + if (!count($accountIdList)) { + $queryBuilder->where([ 'id' => null - ); - } - } + ]); - } + return; + } + + $queryBuilder->where([ + 'id' => $accountIdList + ]); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/Customers.php b/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/Customers.php new file mode 100644 index 0000000000..75522c8678 --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/Customers.php @@ -0,0 +1,43 @@ +where([ + 'type' => 'Customer', + ]); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/Partners.php b/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/Partners.php new file mode 100644 index 0000000000..c572ec221f --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/Partners.php @@ -0,0 +1,43 @@ +where([ + 'type' => 'Partner', + ]); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/RecentlyCreated.php b/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/RecentlyCreated.php new file mode 100644 index 0000000000..b4a406a5e4 --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/RecentlyCreated.php @@ -0,0 +1,50 @@ +modify('-7 days') + ->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); + + $queryBuilder->where([ + 'createdAt>=' => $from, + ]); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/Resellers.php b/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/Resellers.php new file mode 100644 index 0000000000..062735b945 --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Account/PrimaryFilters/Resellers.php @@ -0,0 +1,43 @@ +where([ + 'type' => 'Reseller', + ]); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Call/PrimaryFilters/Todays.php b/application/Espo/Modules/Crm/Classes/Select/Call/PrimaryFilters/Todays.php new file mode 100644 index 0000000000..2c8ab53b1f --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Call/PrimaryFilters/Todays.php @@ -0,0 +1,79 @@ +user = $user; + $this->userTimeZoneProvider = $userTimeZoneProvider; + $this->transformer = $transformer; + $this->itemGeneralConverterFactory = $itemGeneralConverterFactory; + } + + public function apply(SelectBuilder $queryBuilder): void + { + $item = $this->transformer->transform( + Item::fromRaw([ + 'type' => 'today', + 'attribute' => 'dateStart', + 'timeZone' => $this->userTimeZoneProvider->get(), + 'dateTime' => true, + ]) + ); + + $whereItem = $this->itemGeneralConverterFactory + ->create('Call', $this->user) + ->convert($queryBuilder, $item); + + $queryBuilder->where($whereItem); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyOwn.php b/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyOwn.php new file mode 100644 index 0000000000..1581897254 --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyOwn.php @@ -0,0 +1,65 @@ +user = $user; + } + + public function apply(SelectBuilder $queryBuilder): void + { + $queryBuilder + ->distinct() + ->leftJoin('users', 'usersAccess') + ->where( + Cond::or( + Cond::equal( + Cond::column('usersAccessMiddle.userId'), + $this->user->getId() + ), + Cond::equal( + Cond::column('assignedUserId'), + $this->user->getId() + ) + ) + ); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyTeam.php b/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyTeam.php new file mode 100644 index 0000000000..0872b67651 --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyTeam.php @@ -0,0 +1,70 @@ +user = $user; + } + + public function apply(SelectBuilder $queryBuilder): void + { + $queryBuilder + ->distinct() + ->leftJoin('teams', 'teamsAccess') + ->leftJoin('users', 'usersAccess') + ->where( + Cond::or( + Cond::in( + Cond::column('teamsAccessMiddle.teamId'), + $this->user->getLinkMultipleIdList(User::LINK_TEAMS) + ), + Cond::equal( + Cond::column('usersAccessMiddle.userId'), + $this->user->getId() + ), + Cond::equal( + Cond::column('assignedUserId'), + $this->user->getId() + ) + ) + ); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Meeting/BoolFilters/OnlyMy.php b/application/Espo/Modules/Crm/Classes/Select/Meeting/BoolFilters/OnlyMy.php new file mode 100644 index 0000000000..3d53c60e3d --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Meeting/BoolFilters/OnlyMy.php @@ -0,0 +1,73 @@ +user = $user; + } + + public function apply(SelectBuilder $queryBuilder, OrGroupBuilder $orGroupBuilder): void + { + $queryBuilder + ->leftJoin('users', 'usersFilterOnlyMy'); + + $orGroupBuilder + ->add( + Cond::and( + Cond::equal( + Cond::column('usersFilterOnlyMyMiddle.userId'), + $this->user->getId() + ), + Cond::or( + Cond::notEqual( + Cond::column('usersFilterOnlyMyMiddle.status'), + 'Declined' + ), + Cond::equal( + Cond::column('usersFilterOnlyMyMiddle.status'), + null + ) + ) + ) + ); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Meeting/PrimaryFilters/Held.php b/application/Espo/Modules/Crm/Classes/Select/Meeting/PrimaryFilters/Held.php new file mode 100644 index 0000000000..4f8708159e --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Meeting/PrimaryFilters/Held.php @@ -0,0 +1,43 @@ +where([ + 'status' => 'Held', + ]); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Meeting/PrimaryFilters/Planned.php b/application/Espo/Modules/Crm/Classes/Select/Meeting/PrimaryFilters/Planned.php new file mode 100644 index 0000000000..844013315a --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Meeting/PrimaryFilters/Planned.php @@ -0,0 +1,43 @@ +where([ + 'status' => 'Planned', + ]); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Meeting/PrimaryFilters/Todays.php b/application/Espo/Modules/Crm/Classes/Select/Meeting/PrimaryFilters/Todays.php new file mode 100644 index 0000000000..523f1a0d93 --- /dev/null +++ b/application/Espo/Modules/Crm/Classes/Select/Meeting/PrimaryFilters/Todays.php @@ -0,0 +1,79 @@ +user = $user; + $this->userTimeZoneProvider = $userTimeZoneProvider; + $this->transformer = $transformer; + $this->itemGeneralConverterFactory = $itemGeneralConverterFactory; + } + + public function apply(SelectBuilder $queryBuilder): void + { + $item = $this->transformer->transform( + Item::fromRaw([ + 'type' => 'today', + 'attribute' => 'dateStart', + 'timeZone' => $this->userTimeZoneProvider->get(), + 'dateTime' => true, + ]) + ); + + $whereItem = $this->itemGeneralConverterFactory + ->create('Meeting', $this->user) + ->convert($queryBuilder, $item); + + $queryBuilder->where($whereItem); + } +} diff --git a/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Account.json b/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Account.json new file mode 100644 index 0000000000..1813ba9e96 --- /dev/null +++ b/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Account.json @@ -0,0 +1,11 @@ +{ + "primaryFilterClassNameMap": { + "customers": "Espo\\Modules\\Crm\\Classes\\Select\\Account\\PrimaryFilters\\Customers", + "resellers": "Espo\\Modules\\Crm\\Classes\\Select\\Account\\PrimaryFilters\\Resellers", + "partners": "Espo\\Modules\\Crm\\Classes\\Select\\Account\\PrimaryFilters\\Partners", + "recentlyCreated": "Espo\\Modules\\Crm\\Classes\\Select\\Account\\PrimaryFilters\\RecentlyCreated" + }, + "accessControlFilterClassNameMap": { + "portalOnlyAccount": "Espo\\Modules\\Crm\\Classes\\Select\\Account\\AccessControlFilters\\PortalOnlyAccount" + } +} diff --git a/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Call.json b/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Call.json index cf3c360745..7dbfeebd8c 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Call.json +++ b/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Call.json @@ -4,5 +4,17 @@ "dateStart", "dateEnd" ] + }, + "primaryFilterClassNameMap": { + "planned": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\PrimaryFilters\\Planned", + "held": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\PrimaryFilters\\Held", + "todays": "Espo\\Modules\\Crm\\Classes\\Select\\Call\\PrimaryFilters\\Todays" + }, + "boolFilterClassNameMap": { + "onlyMy": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\BoolFilters\\OnlyMy" + }, + "accessControlFilterClassNameMap": { + "onlyOwn": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\AccessControlFilters\\OnlyOwn", + "onlyTeam": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\AccessControlFilters\\OnlyTeam" } -} \ No newline at end of file +} diff --git a/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Meeting.json b/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Meeting.json index 4aedd323a5..0f6e4e78a3 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Meeting.json +++ b/application/Espo/Modules/Crm/Resources/metadata/selectDefs/Meeting.json @@ -5,5 +5,17 @@ "dateStart", "dateEnd" ] + }, + "primaryFilterClassNameMap": { + "planned": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\PrimaryFilters\\Planned", + "held": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\PrimaryFilters\\Held", + "todays": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\PrimaryFilters\\Todays" + }, + "boolFilterClassNameMap": { + "onlyMy": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\BoolFilters\\OnlyMy" + }, + "accessControlFilterClassNameMap": { + "onlyOwn": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\AccessControlFilters\\OnlyOwn", + "onlyTeam": "Espo\\Modules\\Crm\\Classes\\Select\\Meeting\\AccessControlFilters\\OnlyTeam" } -} \ No newline at end of file +} diff --git a/application/Espo/Modules/Crm/SelectManagers/Call.php b/application/Espo/Modules/Crm/SelectManagers/Call.php deleted file mode 100644 index b780c6c18c..0000000000 --- a/application/Espo/Modules/Crm/SelectManagers/Call.php +++ /dev/null @@ -1,102 +0,0 @@ - [ - 'dateStart', - 'dateEnd', - ], - ]; - - protected function accessOnlyOwn(&$result) - { - $this->setDistinct(true, $result); - $this->addJoin(['users', 'usersAccess'], $result); - $result['whereClause'][] = [ - 'OR' => [ - 'usersAccessMiddle.userId' => $this->getUser()->id, - 'assignedUserId' => $this->getUser()->id - ] - ]; - } - - protected function accessOnlyTeam(&$result) - { - $this->setDistinct(true, $result); - $this->addLeftJoin(['teams', 'teamsAccess'], $result); - $this->addLeftJoin(['users', 'usersAccess'], $result); - - $result['whereClause'][] = [ - 'OR' => [ - 'teamsAccessMiddle.teamId' => $this->getUser()->getLinkMultipleIdList('teams'), - 'usersAccessMiddle.userId' => $this->getUser()->id, - 'assignedUserId' => $this->getUser()->id - ] - ]; - } - - protected function boolFilterOnlyMy(&$result) - { - $this->addLeftJoin(['users', 'usersFilterOnlyMy'], $result); - return [ - 'usersFilterOnlyMyMiddle.userId' => $this->getUser()->id, - 'OR' => [ - 'usersFilterOnlyMyMiddle.status!=' => 'Declined', - 'usersFilterOnlyMyMiddle.status=' => null - ] - ]; - } - - protected function filterPlanned(&$result) - { - $result['whereClause'][] = array( - 'status' => 'Planned' - ); - } - - protected function filterHeld(&$result) - { - $result['whereClause'][] = array( - 'status' => 'Held' - ); - } - - protected function filterTodays(&$result) - { - $result['whereClause'][] = $this->convertDateTimeWhere(array( - 'type' => 'today', - 'field' => 'dateStart', - 'timeZone' => $this->getUserTimeZone() - )); - } -} diff --git a/application/Espo/Modules/Crm/SelectManagers/Meeting.php b/application/Espo/Modules/Crm/SelectManagers/Meeting.php deleted file mode 100644 index 5ec70ed9bb..0000000000 --- a/application/Espo/Modules/Crm/SelectManagers/Meeting.php +++ /dev/null @@ -1,172 +0,0 @@ - [ - 'dateStart', - 'dateEnd', - ], - ]; - - protected function accessOnlyOwn(&$result) - { - $this->setDistinct(true, $result); - $this->addJoin(['users', 'usersAccess'], $result); - $result['whereClause'][] = [ - 'OR' => [ - 'usersAccessMiddle.userId' => $this->getUser()->id, - 'assignedUserId' => $this->getUser()->id - ] - ]; - } - - protected function accessOnlyTeam(&$result) - { - $this->setDistinct(true, $result); - $this->addLeftJoin(['teams', 'teamsAccess'], $result); - $this->addLeftJoin(['users', 'usersAccess'], $result); - - $result['whereClause'][] = [ - 'OR' => [ - 'teamsAccessMiddle.teamId' => $this->getUser()->getLinkMultipleIdList('teams'), - 'usersAccessMiddle.userId' => $this->getUser()->id, - 'assignedUserId' => $this->getUser()->id - ] - ]; - } - - protected function boolFilterOnlyMy(&$result) - { - $this->addLeftJoin(['users', 'usersFilterOnlyMy'], $result); - return [ - 'usersFilterOnlyMyMiddle.userId' => $this->getUser()->id, - 'OR' => [ - 'usersFilterOnlyMyMiddle.status!=' => 'Declined', - 'usersFilterOnlyMyMiddle.status=' => null - ] - ]; - } - - protected function filterPlanned(&$result) - { - $result['whereClause'][] = [ - 'status' => 'Planned' - ]; - } - - protected function filterHeld(&$result) - { - $result['whereClause'][] = [ - 'status' => 'Held' - ]; - } - - protected function filterTodays(&$result) - { - $result['whereClause'][] = $this->convertDateTimeWhere([ - 'type' => 'today', - 'attribute' => 'dateStart', - 'timeZone' => $this->getUserTimeZone() - ]); - } - - public function transformDateTimeWhereItem(array $item) : array - { - $where = parent::transformDateTimeWhereItem($item); - - if (empty($where)) { - return []; - } - - $attribute = null; - if (!empty($item['attribute'])) { - $attribute = $item['attribute']; - } - - if ($attribute != 'dateStart' && $attribute != 'dateEnd') return $where; - if (!$this->getSeed()->hasAttribute('dateStartDate')) return $where; - - $type = $item['type'] ?? null; - - if ($type === 'isNull') return $where; - if ($type === 'ever') return $where; - if ($type === 'isNotNull') return $where; - - $attributeDate = $attribute . 'Date'; - - $value = null; - if (array_key_exists('value', $item)) { - $value = $item['value']; - if (is_string($value)) { - if (strlen($value) > 11) { - return $where; - } - } else if (is_array($value)) { - foreach ($value as $valueItem) { - if (strlen($valueItem) > 11) { - return $where; - } - } - } - } - - $dateItem = [ - 'attribute' => $attributeDate, - 'type' => $type, - ]; - - if (array_key_exists('value', $item)) { - $dateItem['value'] = $value; - } - - $where = [ - 'type' => 'or', - 'value' => [ - $dateItem, - [ - 'type' => 'and', - 'value' => [ - $where, - [ - 'type' => 'isNull', - 'attribute' => $attributeDate - ] - ] - - ] - ] - ]; - - return $where; - } -} diff --git a/tests/unit/Espo/Core/Select/Where/ConverterTest.php b/tests/unit/Espo/Core/Select/Where/ConverterTest.php index 2ab814a592..642a6a64d5 100644 --- a/tests/unit/Espo/Core/Select/Where/ConverterTest.php +++ b/tests/unit/Espo/Core/Select/Where/ConverterTest.php @@ -93,7 +93,6 @@ class ConverterTest extends \PHPUnit\Framework\TestCase ->willReturn('Random'); $this->dateTimeItemTransformer = new DateTimeItemTransformer( - $this->entityType, $this->user );