diff --git a/application/Espo/Core/Utils/DataCache.php b/application/Espo/Core/Utils/DataCache.php index 851f2f8b1a..73e3515a8c 100644 --- a/application/Espo/Core/Utils/DataCache.php +++ b/application/Espo/Core/Utils/DataCache.php @@ -86,6 +86,16 @@ class DataCache } } + /** + * Removes in cache. + */ + public function clear(string $key): void + { + $cacheFile = $this->getCacheFile($key); + + $this->fileManager->removeFile($cacheFile); + } + /** * @param mixed $data * @return bool diff --git a/application/Espo/Core/Utils/EmailFilterManager.php b/application/Espo/Core/Utils/EmailFilterManager.php index 422cd49de3..171451ae70 100644 --- a/application/Espo/Core/Utils/EmailFilterManager.php +++ b/application/Espo/Core/Utils/EmailFilterManager.php @@ -31,50 +31,135 @@ namespace Espo\Core\Utils; use Espo\Core\ORM\EntityManager; use Espo\Core\Mail\FiltersMatcher; - use Espo\Entities\Email; use Espo\Entities\EmailFilter; use Espo\Entities\User; use Espo\ORM\Query\Part\Expression; use Espo\ORM\Query\Part\Order; +use LogicException; +use stdClass; /** * Looks for any matching Email Filter for a given email and user. */ class EmailFilterManager { - /** @var array> */ + /** @var array */ private array $data = []; + private bool $useCache; + + private const CACHE_KEY = 'emailFilters'; public function __construct( private EntityManager $entityManager, - private FiltersMatcher $filtersMatcher - ) {} + private FiltersMatcher $filtersMatcher, + private DataCache $dataCache, + Config $config + ) { + $this->useCache = (bool) $config->get('useCache'); + } public function getMatchingFilter(Email $email, string $userId): ?EmailFilter { - if (!array_key_exists($userId, $this->data)) { - $emailFilterList = $this->entityManager - ->getRDBRepository(EmailFilter::ENTITY_TYPE) - ->where([ - 'parentId' => $userId, - 'parentType' => User::ENTITY_TYPE, - ]) - ->order( - Order::createByPositionInList( - Expression::column('action'), - [ - EmailFilter::ACTION_SKIP, - EmailFilter::ACTION_MOVE_TO_FOLDER, - EmailFilter::ACTION_NONE, - ] - ) - ) - ->find(); + $filters = $this->get($userId); - $this->data[$userId] = $emailFilterList; + return $this->filtersMatcher->findMatch($email, $filters); + } + + /** + * @return EmailFilter[] + */ + private function get(string $userId): array + { + if (array_key_exists($userId, $this->data)) { + return $this->data[$userId]; } - return $this->filtersMatcher->findMatch($email, $this->data[$userId]); + $cacheKey = $this->composeCacheKey($userId); + + if ($this->useCache && $this->dataCache->has($cacheKey)) { + $this->data[$userId] = $this->loadFromCache($cacheKey); + + return $this->data[$userId]; + } + + $this->data[$userId] = $this->fetch($userId); + + if ($this->useCache) { + $this->storeToCache($userId); + } + + return $this->data[$userId]; + } + + private function composeCacheKey(string $userId): string + { + return self::CACHE_KEY . '/' . $userId; + } + + /** + * @return EmailFilter[] + */ + private function fetch(string $userId): array + { + $collection = $this->entityManager + ->getRDBRepository(EmailFilter::ENTITY_TYPE) + ->where([ + 'parentId' => $userId, + 'parentType' => User::ENTITY_TYPE, + ]) + ->order( + Order::createByPositionInList( + Expression::column('action'), + [ + EmailFilter::ACTION_SKIP, + EmailFilter::ACTION_MOVE_TO_FOLDER, + EmailFilter::ACTION_NONE, + ] + ) + ) + ->find(); + + return iterator_to_array($collection); + } + + /** + * @return EmailFilter[] + */ + private function loadFromCache(string $cacheKey): array + { + /** @var stdClass[] $dataList */ + $dataList = $this->dataCache->get($cacheKey); + + /** @var EmailFilter[] $list */ + $list = []; + + foreach ($dataList as $item) { + $entity = $this->entityManager->getNewEntity(EmailFilter::ENTITY_TYPE); + + $entity->set($item); + $entity->setAsNotNew(); + + $list[] = $entity; + } + + return $list; + } + + private function storeToCache(string $userId): void + { + if (!array_key_exists($userId, $this->data)) { + throw new LogicException(); + } + + $dataList = []; + + foreach ($this->data[$userId] as $entity) { + $dataList[] = $entity->getValueMap(); + } + + $cacheKey = $this->composeCacheKey($userId); + + $this->dataCache->store($cacheKey, $dataList); } } diff --git a/application/Espo/Hooks/EmailFilter/CacheClearing.php b/application/Espo/Hooks/EmailFilter/CacheClearing.php new file mode 100644 index 0000000000..4d56b648cc --- /dev/null +++ b/application/Espo/Hooks/EmailFilter/CacheClearing.php @@ -0,0 +1,82 @@ + + * @implements AfterRemove + */ +class CacheClearing implements AfterSave, AfterRemove +{ + private const CACHE_KEY = 'emailFilters'; + + public function __construct(private DataCache $dataCache) {} + + /** + * @param EmailFilter $entity + */ + public function afterSave(Entity $entity, SaveOptions $options): void + { + $this->processEntity($entity); + } + + /** + * @param EmailFilter $entity + */ + public function afterRemove(Entity $entity, RemoveOptions $options): void + { + $this->processEntity($entity); + } + + private function processEntity(EmailFilter $entity): void + { + if ($entity->getParentType() !== User::ENTITY_TYPE || !$entity->getParentId()) { + return; + } + + $cacheKey = $this->composeCacheKey($entity->getParentId()); + + $this->dataCache->clear($cacheKey); + } + + private function composeCacheKey(string $userId): string + { + return self::CACHE_KEY . '/' . $userId; + } +}