diff --git a/application/Espo/Core/Formula/Functions/EntityGroup/CountRelatedType.php b/application/Espo/Core/Formula/Functions/EntityGroup/CountRelatedType.php index ac23b8ae83..ffd5350004 100644 --- a/application/Espo/Core/Formula/Functions/EntityGroup/CountRelatedType.php +++ b/application/Espo/Core/Formula/Functions/EntityGroup/CountRelatedType.php @@ -32,23 +32,19 @@ namespace Espo\Core\Formula\Functions\EntityGroup; use Espo\Core\Exceptions\BadRequest; use Espo\Core\Exceptions\Forbidden; use Espo\Core\Formula\Exceptions\Error; +use Espo\Core\Formula\Functions\Base; +use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; use Espo\ORM\Defs\Params\RelationParam; -use Espo\ORM\EntityManager; use Espo\Core\Di; use stdClass; -class CountRelatedType extends \Espo\Core\Formula\Functions\Base implements +class CountRelatedType extends Base implements Di\EntityManagerAware, Di\SelectBuilderFactoryAware { use Di\EntityManagerSetter; use Di\SelectBuilderFactorySetter; - /** - * @var EntityManager - */ - protected $entityManager; - /** * @return int * @throws Error @@ -86,7 +82,7 @@ class CountRelatedType extends \Espo\Core\Formula\Functions\Base implements ->from($foreignEntityType); if ($filter) { - $builder->withPrimaryFilter($filter); + (new FindQueryUtil())->applyFilter($builder, $filter, 2); } try { diff --git a/application/Espo/Core/Formula/Functions/EntityGroup/IsRelatedType.php b/application/Espo/Core/Formula/Functions/EntityGroup/IsRelatedType.php index 0d2d378894..37099cf895 100644 --- a/application/Espo/Core/Formula/Functions/EntityGroup/IsRelatedType.php +++ b/application/Espo/Core/Formula/Functions/EntityGroup/IsRelatedType.php @@ -29,25 +29,18 @@ namespace Espo\Core\Formula\Functions\EntityGroup; -use Espo\Core\Exceptions\Error; - -use Espo\ORM\EntityManager; +use Espo\Core\Formula\Exceptions\Error; +use Espo\Core\Formula\Functions\Base; use Espo\Core\Di; -class IsRelatedType extends \Espo\Core\Formula\Functions\Base implements +class IsRelatedType extends Base implements Di\EntityManagerAware { use Di\EntityManagerSetter; - /** - * @var EntityManager - */ - protected $entityManager; - /** * @return bool - * @throws \Espo\Core\Formula\Exceptions\Error * @throws Error */ public function process(\stdClass $item) diff --git a/application/Espo/Core/Formula/Functions/EntityGroup/SumRelatedType.php b/application/Espo/Core/Formula/Functions/EntityGroup/SumRelatedType.php index aadb5a9521..551d149b6d 100644 --- a/application/Espo/Core/Formula/Functions/EntityGroup/SumRelatedType.php +++ b/application/Espo/Core/Formula/Functions/EntityGroup/SumRelatedType.php @@ -29,16 +29,18 @@ namespace Espo\Core\Formula\Functions\EntityGroup; -use Espo\Core\Exceptions\Error; - +use Espo\Core\Exceptions\BadRequest; +use Espo\Core\Formula\Exceptions\Error; use Espo\Core\Di; - +use Espo\Core\Exceptions\Forbidden; +use Espo\Core\Formula\Functions\Base; +use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; use Espo\ORM\Defs\Params\RelationParam; use Espo\ORM\Name\Attribute; use stdClass; use PDO; -class SumRelatedType extends \Espo\Core\Formula\Functions\Base implements +class SumRelatedType extends Base implements Di\EntityManagerAware, Di\SelectBuilderFactoryAware { @@ -48,7 +50,6 @@ class SumRelatedType extends \Espo\Core\Formula\Functions\Base implements /** * @return float * @throws Error - * @throws \Espo\Core\Formula\Exceptions\Error */ public function process(stdClass $item) { @@ -96,10 +97,14 @@ class SumRelatedType extends \Espo\Core\Formula\Functions\Base implements ->from($foreignEntityType); if ($filter) { - $builder->withPrimaryFilter($filter); + (new FindQueryUtil())->applyFilter($builder, $filter, 3); } - $queryBuilder = $builder->buildQueryBuilder(); + try { + $queryBuilder = $builder->buildQueryBuilder(); + } catch (BadRequest|Forbidden $e) { + throw new Error($e->getMessage(), $e->getCode(), $e); + } $queryBuilder->select([ [$foreignLinkAlias . '.id', 'foreignId'], diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/CountType.php b/application/Espo/Core/Formula/Functions/RecordGroup/CountType.php index 870bb897b7..7969a577e1 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/CountType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/CountType.php @@ -29,11 +29,12 @@ namespace Espo\Core\Formula\Functions\RecordGroup; -use Espo\Core\Formula\{ - Functions\BaseFunction, - ArgumentList, -}; - +use Espo\Core\Exceptions\BadRequest; +use Espo\Core\Exceptions\Forbidden; +use Espo\Core\Formula\ArgumentList; +use Espo\Core\Formula\Exceptions\Error; +use Espo\Core\Formula\Functions\BaseFunction; +use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; use Espo\Core\Di; class CountType extends BaseFunction implements @@ -54,7 +55,7 @@ class CountType extends BaseFunction implements if (count($args) < 3) { $filter = null; - if (count($args) == 2) { + if (count($args) === 2) { $filter = $this->evaluate($args[1]); } @@ -62,18 +63,16 @@ class CountType extends BaseFunction implements ->create() ->from($entityType); - if ($filter && !is_string($filter)) { - $this->throwBadArgumentType(2, 'string'); - } + (new FindQueryUtil())->applyFilter($builder, $filter, 2); - if ($filter) { - $builder->withPrimaryFilter($filter); + try { + return $this->entityManager + ->getRDBRepository($entityType) + ->clone($builder->build()) + ->count(); + } catch (BadRequest|Forbidden $e) { + throw new Error($e->getMessage(), 0, $e); } - - return $this->entityManager - ->getRDBRepository($entityType) - ->clone($builder->build()) - ->count(); } $whereClause = []; diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/ExistsType.php b/application/Espo/Core/Formula/Functions/RecordGroup/ExistsType.php index ec9126ce36..779c51b8c0 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/ExistsType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/ExistsType.php @@ -29,40 +29,62 @@ namespace Espo\Core\Formula\Functions\RecordGroup; -use Espo\ORM\EntityManager; - -use Espo\Core\Formula\{ - Functions\BaseFunction, - ArgumentList, -}; - +use Espo\Core\Exceptions\BadRequest; +use Espo\Core\Exceptions\Forbidden; +use Espo\Core\Formula\ArgumentList; +use Espo\Core\Formula\Exceptions\Error; +use Espo\Core\Formula\Functions\BaseFunction; use Espo\Core\Di; +use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; class ExistsType extends BaseFunction implements - Di\EntityManagerAware + Di\EntityManagerAware, + Di\SelectBuilderFactoryAware { use Di\EntityManagerSetter; - - /** - * @var EntityManager - */ - protected $entityManager; + use Di\SelectBuilderFactorySetter; public function process(ArgumentList $args) { - if (count($args) < 3) { - $this->throwTooFewArguments(3); + if (count($args) < 1) { + $this->throwTooFewArguments(1); } $entityType = $this->evaluate($args[0]); + if (count($args) <= 2) { + $filter = null; + + if (count($args) === 2) { + $filter = $this->evaluate($args[1]); + } + + $builder = $this->selectBuilderFactory + ->create() + ->from($entityType); + + (new FindQueryUtil())->applyFilter($builder, $filter, 2); + + try { + return (bool) $this->entityManager + ->getRDBRepository($entityType) + ->clone($builder->build()) + ->findOne(); + } catch (BadRequest|Forbidden $e) { + throw new Error($e->getMessage(), 0, $e); + } + } + $whereClause = []; $i = 1; + while ($i < count($args) - 1) { $key = $this->evaluate($args[$i]); $value = $this->evaluate($args[$i + 1]); + $whereClause[] = [$key => $value]; + $i = $i + 2; } diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/FindManyType.php b/application/Espo/Core/Formula/Functions/RecordGroup/FindManyType.php index 9d5b2bcc19..3ef1d2f61c 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/FindManyType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/FindManyType.php @@ -33,9 +33,11 @@ use Espo\Core\Exceptions\BadRequest; use Espo\Core\Exceptions\Forbidden; use Espo\Core\Formula\EvaluatedArgumentList; use Espo\Core\Formula\Exceptions\BadArgumentType; +use Espo\Core\Formula\Exceptions\BadArgumentValue; use Espo\Core\Formula\Exceptions\Error as FormulaError; use Espo\Core\Formula\Exceptions\TooFewArguments; use Espo\Core\Formula\Func; +use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; use Espo\Core\Select\SelectBuilderFactory; use Espo\ORM\Entity; use Espo\ORM\EntityManager; @@ -76,10 +78,18 @@ class FindManyType implements Func throw BadArgumentType::create(3, 'string|null'); } - if (!is_bool($order) && !is_string($orderBy)) { + if (!is_bool($order) && !is_string($order)) { throw BadArgumentType::create(4, 'string|bool'); } + if (is_string($order)) { + $order = strtoupper($order); + + if ($order !== Order::ASC && $order !== Order::DESC) { + throw BadArgumentValue::create(4, 'Bad order value.'); + } + } + $builder = $this->selectBuilderFactory ->create() ->from($entityType); @@ -93,13 +103,7 @@ class FindManyType implements Func $filter = $arguments[4]; } - if ($filter && !is_string($filter)) { - throw BadArgumentType::create(5, 'string'); - } - - if ($filter) { - $builder->withPrimaryFilter($filter); - } + (new FindQueryUtil())->applyFilter($builder, $filter, 5); } else { $i = 4; diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/FindOneType.php b/application/Espo/Core/Formula/Functions/RecordGroup/FindOneType.php index cd4802bc9b..3325dd14c2 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/FindOneType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/FindOneType.php @@ -35,8 +35,13 @@ use Espo\Core\Formula\ArgumentList; use Espo\Core\Formula\Exceptions\Error as FormulaError; use Espo\Core\Formula\Functions\BaseFunction; use Espo\Core\Di; +use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; use Espo\ORM\Name\Attribute; +use Espo\ORM\Query\Part\Order; +/** + * @noinspection PhpUnused + */ class FindOneType extends BaseFunction implements Di\EntityManagerAware, Di\SelectBuilderFactoryAware @@ -52,7 +57,7 @@ class FindOneType extends BaseFunction implements $entityType = $this->evaluate($args[0]); $orderBy = $this->evaluate($args[1]); - $order = $this->evaluate($args[2]) ?? 'ASC'; + $order = $this->evaluate($args[2]) ?? Order::ASC; $builder = $this->selectBuilderFactory ->create() @@ -63,17 +68,11 @@ class FindOneType extends BaseFunction implements if (count($args) <= 4) { $filter = null; - if (count($args) == 4) { + if (count($args) === 4) { $filter = $this->evaluate($args[3]); } - if ($filter && !is_string($filter)) { - $this->throwBadArgumentType(4, 'string'); - } - - if ($filter) { - $builder->withPrimaryFilter($filter); - } + (new FindQueryUtil())->applyFilter($builder, $filter, 4); } else { $i = 3; diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedManyType.php b/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedManyType.php index a31f144212..972bfe5fd0 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedManyType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedManyType.php @@ -35,6 +35,7 @@ use Espo\Core\Formula\Exceptions\BadArgumentType; use Espo\Core\Formula\Exceptions\Error; use Espo\Core\Formula\Exceptions\ExecutionException; use Espo\Core\Formula\Exceptions\TooFewArguments; +use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; use Espo\Core\ORM\Entity as CoreEntity; use Espo\Core\Formula\ArgumentList; use Espo\Core\Formula\Functions\BaseFunction; @@ -42,6 +43,7 @@ use Espo\Core\Di; use Espo\Core\Select\Helpers\RandomStringGenerator; use Espo\ORM\Defs\Params\RelationParam; use Espo\ORM\Name\Attribute; +use Espo\ORM\Type\RelationType; class FindRelatedManyType extends BaseFunction implements Di\EntityManagerAware, @@ -56,10 +58,8 @@ class FindRelatedManyType extends BaseFunction implements /** * @throws Error - * @throws BadRequest * @throws TooFewArguments * @throws BadArgumentType - * @throws Forbidden * @throws ExecutionException */ public function process(ArgumentList $args) @@ -136,7 +136,13 @@ class FindRelatedManyType extends BaseFunction implements $relationType = $entity->getRelationParam($link, 'type'); - if (in_array($relationType, ['belongsTo', 'hasOne', 'belongsToParent'])) { + if ( + in_array($relationType, [ + RelationType::BELONGS_TO, + RelationType::HAS_ONE, + RelationType::BELONGS_TO_PARENT, + ]) + ) { $this->throwError("Not supported link type '$relationType'."); } @@ -164,13 +170,7 @@ class FindRelatedManyType extends BaseFunction implements $filter = $args[6]; } - if ($filter && !is_string($filter)) { - $this->throwError("Bad filter."); - } - - if ($filter) { - $builder->withPrimaryFilter($filter); - } + (new FindQueryUtil())->applyFilter($builder, $filter, 7); } else { $i = 6; @@ -184,13 +184,17 @@ class FindRelatedManyType extends BaseFunction implements } } - $queryBuilder = $builder->buildQueryBuilder(); + try { + $queryBuilder = $builder->buildQueryBuilder(); + } catch (BadRequest|Forbidden $e) { + throw new Error($e->getMessage(), 0, $e); + } if (!empty($whereClause)) { $queryBuilder->where($whereClause); } - if ($relationType === 'hasChildren') { + if ($relationType === RelationType::HAS_CHILDREN) { $queryBuilder->where([ $foreignLink . 'Id' => $entity->getId(), $foreignLink . 'Type' => $entity->getEntityType(), diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedOneType.php b/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedOneType.php index 4380ccc2f2..9962cd0491 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedOneType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedOneType.php @@ -29,16 +29,18 @@ namespace Espo\Core\Formula\Functions\RecordGroup; +use Espo\Core\Exceptions\BadRequest; +use Espo\Core\Exceptions\Forbidden; use Espo\Core\ORM\Entity as CoreEntity; - use Espo\ORM\Defs\Params\RelationParam; use Espo\ORM\Name\Attribute; -use Espo\Core\Formula\{ - Functions\BaseFunction, - ArgumentList, -}; - +use Espo\Core\Formula\ArgumentList; +use Espo\Core\Formula\Exceptions\Error; +use Espo\Core\Formula\Functions\BaseFunction; +use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; use Espo\Core\Di; +use Espo\ORM\Query\Part\Order; +use Espo\ORM\Type\RelationType; class FindRelatedOneType extends BaseFunction implements Di\EntityManagerAware, @@ -98,7 +100,13 @@ class FindRelatedOneType extends BaseFunction implements $relationType = $entity->getRelationParam($link, 'type'); - if (in_array($relationType, ['belongsTo', 'hasOne', 'belongsToParent'])) { + if ( + in_array($relationType, [ + RelationType::BELONGS_TO, + RelationType::HAS_ONE, + RelationType::BELONGS_TO_PARENT, + ]) + ) { $relatedEntity = $entityManager ->getRDBRepository($entityType) ->getRelation($entity, $link) @@ -119,7 +127,7 @@ class FindRelatedOneType extends BaseFunction implements $order = $metadata->get(['entityDefs', $entityType, 'collection', 'order']) ?? 'ASC'; } } else { - $order = $order ?? 'ASC'; + $order = $order ?? Order::ASC; } $foreignEntityType = $entity->getRelationParam($link, RelationParam::ENTITY); @@ -143,17 +151,11 @@ class FindRelatedOneType extends BaseFunction implements if (count($args) <= 6) { $filter = null; - if (count($args) == 6) { + if (count($args) === 6) { $filter = $this->evaluate($args[5]); } - if ($filter && !is_string($filter)) { - $this->throwError("Bad filter."); - } - - if ($filter) { - $builder->withPrimaryFilter($filter); - } + (new FindQueryUtil())->applyFilter($builder, $filter, 6); } else { $i = 5; @@ -167,13 +169,17 @@ class FindRelatedOneType extends BaseFunction implements } } - $queryBuilder = $builder->buildQueryBuilder(); + try { + $queryBuilder = $builder->buildQueryBuilder(); + } catch (BadRequest|Forbidden $e) { + throw new Error($e->getMessage(), 0, $e); + } if (!empty($whereClause)) { $queryBuilder->where($whereClause); } - if ($relationType === 'hasChildren') { + if ($relationType === RelationType::HAS_CHILDREN) { $queryBuilder->where([ $foreignLink . 'Id' => $entity->getId(), $foreignLink . 'Type' => $entity->getEntityType(), diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/Util/FindQueryUtil.php b/application/Espo/Core/Formula/Functions/RecordGroup/Util/FindQueryUtil.php new file mode 100644 index 0000000000..2b6d5251c3 --- /dev/null +++ b/application/Espo/Core/Formula/Functions/RecordGroup/Util/FindQueryUtil.php @@ -0,0 +1,74 @@ +. + * + * 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\Formula\Functions\RecordGroup\Util; + +use Espo\Core\Formula\Exceptions\BadArgumentType; +use Espo\Core\Formula\Exceptions\Error; +use Espo\Core\Select\SelectBuilder; +use Espo\Core\Select\Where\Item; +use Espo\Core\Utils\Json; +use InvalidArgumentException; +use stdClass; + +class FindQueryUtil +{ + public function __construct() {} + + /** + * @throws Error + * @throws BadArgumentType + */ + public function applyFilter(SelectBuilder $builder, mixed $filter, int $position): void + { + if (is_string($filter)) { + $builder->withPrimaryFilter($filter); + + return; + } + + if ($filter instanceof stdClass) { + $rawWhere = Json::decode(Json::encode($filter), true); + + try { + $where = Item::fromRaw($rawWhere); + } catch (InvalidArgumentException $e) { + throw new Error($e->getMessage(), 0, $e); + } + + $builder->withWhere($where); + + return; + } + + if ($filter) { + throw BadArgumentType::create($position, 'string|object'); + } + } +} diff --git a/tests/integration/Espo/Core/Formula/FormulaTest.php b/tests/integration/Espo/Core/Formula/FormulaTest.php index a228c430b3..a2c1d539cd 100644 --- a/tests/integration/Espo/Core/Formula/FormulaTest.php +++ b/tests/integration/Espo/Core/Formula/FormulaTest.php @@ -36,6 +36,7 @@ use Espo\Core\Formula\Exceptions\UnsafeFunction; use Espo\Core\Formula\Manager; use Espo\Entities\User; use Espo\Modules\Crm\Entities\Account; +use Espo\Modules\Crm\Entities\Contact; use Espo\Modules\Crm\Entities\Meeting; use Espo\Modules\Crm\Entities\Opportunity; use Espo\ORM\EntityManager; @@ -1016,4 +1017,159 @@ class FormulaTest extends BaseTestCase /** @noinspection PhpUnhandledExceptionInspection */ $fm->runSafe($script); } + + /** + * @noinspection PhpUnhandledExceptionInspection + */ + public function testFilterWhere(): void + { + $em = $this->getEntityManager(); + $fm = $this->getContainer()->getByClass(Manager::class); + + $account1 = $em->createEntity(Account::ENTITY_TYPE, ['name' => 'a1']); + + $em->createEntity(Account::ENTITY_TYPE, ['name' => 'a2']); + + $contact1 = $em->createEntity(Contact::ENTITY_TYPE, [ + 'lastName' => 'c1_1', + 'accountId' => $account1->getId(), + ]); + + $em->createEntity(Contact::ENTITY_TYPE, [ + 'lastName' => 'c1_2', + 'accountId' => $account1->getId(), + ]); + + $em->createEntity(Opportunity::ENTITY_TYPE, [ + 'accountId' => $account1->getId(), + 'amount' => 100.0, + 'name' => 'o1_1', + ]); + + $em->createEntity(Opportunity::ENTITY_TYPE, [ + 'accountId' => $account1->getId(), + 'amount' => 150.0, + 'name' => 'o1_2', + ]); + + $script = " + \$where = object\create(\$w); + \$where['type'] = 'and'; + \$where['value'] = list( + ( + \$item1 = object\create(); + \$item1['type'] = 'equals'; + \$item1['attribute'] = 'name'; + \$item1['value'] = 'a1'; + \$item1; + ) + ); + + record\\findMany('Account', 2, null, null, \$where); + "; + $result = $fm->run($script); + $this->assertEquals([$account1->getId()], $result); + + $script = " + \$where = object\create(\$w); + \$where['type'] = 'and'; + \$where['value'] = list( + ( + \$item1 = object\create(); + \$item1['type'] = 'equals'; + \$item1['attribute'] = 'name'; + \$item1['value'] = 'a1'; + \$item1; + ) + ); + + record\\findOne('Account', null, null, \$where); + "; + $result = $fm->run($script); + $this->assertEquals($account1->getId(), $result); + + $script = " + \$where = object\create(\$w); + \$where['type'] = 'and'; + \$where['value'] = list( + ( + \$item1 = object\create(); + \$item1['type'] = 'equals'; + \$item1['attribute'] = 'name'; + \$item1['value'] = 'a1'; + \$item1; + ) + ); + + record\\exists('Account', \$where); + "; + $result = $fm->run($script); + $this->assertTrue($result); + + $script = " + \$where = object\create(\$w); + \$where['type'] = 'and'; + \$where['value'] = list( + ( + \$item1 = object\create(); + \$item1['type'] = 'equals'; + \$item1['attribute'] = 'name'; + \$item1['value'] = 'a1'; + \$item1; + ) + ); + + record\\count('Account', \$where); + "; + $result = $fm->run($script); + $this->assertEquals(1, $result); + + $script = " + \$item = object\create(); + \$item['type'] = 'equals'; + \$item['attribute'] = 'lastName'; + \$item['value'] = 'c1_1'; + \$item; + + record\\findRelatedOne('Account', '{$account1->getId()}', 'contacts', null, null, \$item); + "; + $result = $fm->run($script); + $this->assertEquals($contact1->getId(), $result); + + $script = " + \$item = object\create(); + \$item['type'] = 'equals'; + \$item['attribute'] = 'lastName'; + \$item['value'] = 'c1_1'; + \$item; + + record\\findRelatedMany('Account', '{$account1->getId()}', 'contacts', 2, null, null, \$item); + "; + $result = $fm->run($script); + $this->assertEquals([$contact1->getId()], $result); + + $script = " + \$item = object\create(); + \$item['type'] = 'equals'; + \$item['attribute'] = 'lastName'; + \$item['value'] = 'c1_1'; + \$item; + + entity\\countRelated('contacts', \$item); + "; + $result = $fm->run($script, $account1); + $this->assertEquals(1, $result); + + $script = " + \$item = object\create(); + \$item['type'] = 'equals'; + \$item['attribute'] = 'name'; + \$item['value'] = 'o1_1'; + \$item; + + entity\\sumRelated('opportunities', 'amount', \$item); + "; + $result = $fm->run($script, $account1); + $this->assertEquals(100.0, $result); + } }