diff --git a/application/Espo/Core/Controllers/Record.php b/application/Espo/Core/Controllers/Record.php index 1cb5d160fc..007e375981 100644 --- a/application/Espo/Core/Controllers/Record.php +++ b/application/Espo/Core/Controllers/Record.php @@ -31,6 +31,7 @@ namespace Espo\Core\Controllers; use Espo\Core\Exceptions\BadRequest; use Espo\Core\Api\Request; +use Espo\Core\Exceptions\Error; use Espo\Core\Exceptions\Forbidden; use Espo\Core\Exceptions\NotFound; use Espo\Core\Exceptions\NotFoundSilent; @@ -77,6 +78,7 @@ class Record extends RecordBase * @throws BadRequest * @throws Forbidden * @throws NotFound + * @throws Error */ public function postActionCreateLink(Request $request): bool { diff --git a/application/Espo/Core/Record/Collection.php b/application/Espo/Core/Record/Collection.php index d0205cff8c..c11614ec7a 100644 --- a/application/Espo/Core/Record/Collection.php +++ b/application/Espo/Core/Record/Collection.php @@ -37,7 +37,7 @@ use stdClass; /** * Contains an ORM collection and total number of records. * - * @template TEntity of \Espo\ORM\Entity + * @template-covariant TEntity of \Espo\ORM\Entity */ class Collection { diff --git a/application/Espo/Core/Record/Service.php b/application/Espo/Core/Record/Service.php index 055167ae8e..321f57c9bc 100644 --- a/application/Espo/Core/Record/Service.php +++ b/application/Espo/Core/Record/Service.php @@ -251,7 +251,7 @@ class Service implements Crud, $entity = $this->getEntity($id); if (!$entity) { - throw new NotFoundSilent("Record {$id} does not exist."); + throw new NotFoundSilent("Record $id does not exist."); } $this->recordHookManager->processBeforeRead($entity, $params); @@ -712,7 +712,7 @@ class Service implements Crud, $this->getRepository()->getById($id); if (!$entity) { - throw new NotFound("Record {$id} not found."); + throw new NotFound("Record $id not found."); } if (!$this->getEntityBeforeUpdate) { @@ -773,7 +773,7 @@ class Service implements Crud, $entity = $this->getRepository()->getById($id); if (!$entity) { - throw new NotFound("Record {$id} not found."); + throw new NotFound("Record $id not found."); } if (!$this->acl->check($entity, AclTable::ACTION_DELETE)) { @@ -946,10 +946,8 @@ class Service implements Crud, $this->processForbiddenLinkReadCheck($link); - $methodName = 'findLinked' . ucfirst($link); - - if (method_exists($this, $methodName)) { - return $this->$methodName($id, $searchParams); + if ($methodResult = $this->processFindLinkedMethod($id, $link, $searchParams)) { + return $methodResult; } $foreignEntityType = $this->entityManager @@ -1028,6 +1026,27 @@ class Service implements Crud, return RecordCollection::create($collection, $total); } + /** + * @param string $link + * @return ?RecordCollection + * @throws Forbidden + * @throws NotFound + */ + private function processFindLinkedMethod(string $id, string $link, SearchParams $searchParams): ?RecordCollection + { + if ($link === 'followers') { + return $this->findLinkedFollowers($id, $searchParams); + } + + $methodName = 'findLinked' . ucfirst($link); + + if (method_exists($this, $methodName)) { + return $this->$methodName($id, $searchParams); + } + + return null; + } + /** * Link records. * @@ -1059,18 +1078,14 @@ class Service implements Crud, $this->getLinkCheck()->processLink($entity, $link); - $methodName = 'link' . ucfirst($link); - - if ($link !== 'entity' && $link !== 'entityMass' && method_exists($this, $methodName)) { - $this->$methodName($id, $foreignId); - + if ($this->processLinkMethod($id, $link, $foreignId)) { return; } $foreignEntityType = $entity->getRelationParam($link, 'entity'); if (!$foreignEntityType) { - throw new LogicException("Entity '{$this->entityType}' has not relation '{$link}'."); + throw new LogicException("Entity '$this->entityType' has not relation '$link'."); } $foreignEntity = $this->entityManager->getEntityById($foreignEntityType, $foreignId); @@ -1119,18 +1134,14 @@ class Service implements Crud, $this->getLinkCheck()->processLink($entity, $link); - $methodName = 'unlink' . ucfirst($link); - - if ($link !== 'entity' && method_exists($this, $methodName)) { - $this->$methodName($id, $foreignId); - + if ($this->processUnlinkMethod($id, $link, $foreignId)) { return; } $foreignEntityType = $entity->getRelationParam($link, 'entity'); if (!$foreignEntityType) { - throw new LogicException("Entity '{$this->entityType}' has not relation '{$link}'."); + throw new LogicException("Entity '$this->entityType' has not relation '$link'."); } $foreignEntity = $this->entityManager->getEntityById($foreignEntityType, $foreignId); @@ -1148,12 +1159,65 @@ class Service implements Crud, ->unrelate($foreignEntity); } + /** + * @throws Forbidden + * @throws NotFound + */ + private function processLinkMethod(string $id, string $link, string $foreignId): bool + { + if ($link === 'followers') { + $this->linkFollowers($id, $foreignId); + + return true; + } + + $methodName = 'link' . ucfirst($link); + + if ( + $link !== 'entity' && + $link !== 'entityMass' && + method_exists($this, $methodName) + ) { + $this->$methodName($id, $foreignId); + + return true; + } + + return false; + } + + /** + * @throws Forbidden + * @throws NotFound + */ + private function processUnlinkMethod(string $id, string $link, string $foreignId): bool + { + if ($link === 'followers') { + $this->unlinkFollowers($id, $foreignId); + + return true; + } + + $methodName = 'unlink' . ucfirst($link); + + if ( + $link !== 'entity' && + method_exists($this, $methodName) + ) { + $this->$methodName($id, $foreignId); + + return true; + } + + return false; + } + /** * @throws Forbidden * @throws NotFound * @throws ForbiddenSilent */ - public function linkFollowers(string $id, string $foreignId): void + protected function linkFollowers(string $id, string $foreignId): void { if (!$this->acl->check($this->entityType, AclTable::ACTION_EDIT)) { throw new Forbidden(); @@ -1169,8 +1233,8 @@ class Service implements Crud, throw new NotFound(); } - /** @var User|null $user */ - $user = $this->entityManager->getEntity(User::ENTITY_TYPE, $foreignId); + /** @var ?User $user */ + $user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $foreignId); if (!$user) { throw new NotFound(); @@ -1223,7 +1287,7 @@ class Service implements Crud, * @throws NotFound * @throws ForbiddenSilent */ - public function unlinkFollowers(string $id, string $foreignId): void + protected function unlinkFollowers(string $id, string $foreignId): void { if (!$this->acl->check($this->entityType, AclTable::ACTION_EDIT)) { throw new Forbidden(); @@ -1277,6 +1341,7 @@ class Service implements Crud, * @throws BadRequest * @throws Forbidden * @throws NotFound + * @throws Error */ public function massLink(string $id, string $link, SearchParams $searchParams): bool { @@ -1313,7 +1378,7 @@ class Service implements Crud, $foreignEntityType = $entity->getRelationParam($link, 'entity'); if (empty($foreignEntityType)) { - throw new LogicException("Link '{$link}' has no 'entity'."); + throw new LogicException("Link '$link' has no 'entity'."); } $accessActionRequired = AclTable::ACTION_EDIT; @@ -1372,7 +1437,7 @@ class Service implements Crud, protected function processForbiddenLinkReadCheck(string $link): void { $forbiddenLinkList = $this->acl - ->getScopeForbiddenLinkList($this->entityType, AclTable::ACTION_READ); + ->getScopeForbiddenLinkList($this->entityType); if (in_array($link, $forbiddenLinkList)) { throw new Forbidden(); @@ -1554,8 +1619,7 @@ class Service implements Crud, } } - $forbiddenAttributeList = $this->acl - ->getScopeForbiddenAttributeList($entity->getEntityType(), AclTable::ACTION_READ); + $forbiddenAttributeList = $this->acl->getScopeForbiddenAttributeList($entity->getEntityType()); foreach ($forbiddenAttributeList as $attribute) { $entity->clear($attribute); @@ -1667,12 +1731,16 @@ class Service implements Crud, ->find(); foreach ($linkedList as $linked) { - $repository->relate($entity, $link, $linked); + $repository + ->getRelation($entity, $link) + ->relate($linked); } } } /** + * @deprecated + * @todo Remove in v7.6. * @param string $type * @return string[] */ diff --git a/application/Espo/ORM/Collection.php b/application/Espo/ORM/Collection.php index 60cd5ded9e..6feaf58192 100644 --- a/application/Espo/ORM/Collection.php +++ b/application/Espo/ORM/Collection.php @@ -35,7 +35,7 @@ use stdClass; /** * A collection of entities. * - * @template TEntity of Entity + * @template-covariant TEntity of Entity * @extends Traversable */ interface Collection extends Traversable