mirror of
https://github.com/espocrm/espocrm.git
synced 2026-06-28 15:06:06 +00:00
configurator restriction (#3616)
This commit is contained in:
38
application/Espo/Core/Acl/Exceptions/Restricted.php
Normal file
38
application/Espo/Core/Acl/Exceptions/Restricted.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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\Acl\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @since 9.4.0
|
||||
*/
|
||||
class Restricted extends Exception
|
||||
{}
|
||||
231
application/Espo/Core/Acl/SystemRestriction.php
Normal file
231
application/Espo/Core/Acl/SystemRestriction.php
Normal file
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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\Acl;
|
||||
|
||||
use Espo\Core\Acl\Exceptions\Restricted;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\AppLogRecord;
|
||||
use Espo\Entities\ArrayValue;
|
||||
use Espo\Entities\Extension;
|
||||
use Espo\Entities\MassAction;
|
||||
use Espo\Entities\PasswordChangeRequest;
|
||||
use Espo\Entities\SystemData;
|
||||
use Espo\Entities\TwoFactorCode;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @since 9.4.0
|
||||
*/
|
||||
class SystemRestriction
|
||||
{
|
||||
/** @var string[] */
|
||||
private array $writeForbiddenEntityTypeList = [
|
||||
Extension::ENTITY_TYPE,
|
||||
AppLogRecord::ENTITY_TYPE,
|
||||
PasswordChangeRequest::ENTITY_TYPE,
|
||||
TwoFactorCode::ENTITY_TYPE,
|
||||
SystemData::ENTITY_TYPE,
|
||||
MassAction::ENTITY_TYPE,
|
||||
ArrayValue::ENTITY_TYPE,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private GlobalRestriction $globalRestriction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Restricted
|
||||
*/
|
||||
public function assertUpdate(Entity $entity): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->checkEntityTypeWrite($entity->getEntityType())) {
|
||||
throw new Restricted("Cannot write '$entityType' entity.");
|
||||
}
|
||||
|
||||
if ($entity instanceof User) {
|
||||
$this->assertUserUpdate($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Restricted
|
||||
*/
|
||||
public function assertRemoval(Entity $entity): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->checkEntityTypeWrite($entity->getEntityType())) {
|
||||
throw new Restricted("Cannot remove '$entityType' entity.");
|
||||
}
|
||||
|
||||
if ($entity instanceof User) {
|
||||
$this->assertRemovalUser($entity);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkEntityTypeWrite(string $entityType): bool
|
||||
{
|
||||
if (in_array($entityType, $this->writeForbiddenEntityTypeList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->metadata->get("entityAcl.$entityType.systemWriteForbidden")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getUserRestrictedTypeList(): array
|
||||
{
|
||||
return [
|
||||
User::TYPE_SUPER_ADMIN,
|
||||
User::TYPE_SYSTEM,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Restricted
|
||||
*/
|
||||
private function assertUserUpdate(User $entity): void
|
||||
{
|
||||
$restrictedTypeList = self::getUserRestrictedTypeList();
|
||||
|
||||
if (
|
||||
$entity->isAttributeChanged(User::ATTR_TYPE) &&
|
||||
(
|
||||
in_array($entity->getFetched(User::ATTR_TYPE), $restrictedTypeList) ||
|
||||
in_array($entity->getType(), $restrictedTypeList)
|
||||
)
|
||||
) {
|
||||
throw new Restricted("Cannot change user type.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Restricted
|
||||
*/
|
||||
private function assertRemovalUser(User $entity): void
|
||||
{
|
||||
if (in_array($entity->getType(), self::getUserRestrictedTypeList())) {
|
||||
throw new Restricted("Cannot remove {$entity->getId()} user.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getReadRestrictedAttributeList(string $entityType): array
|
||||
{
|
||||
$list1 = $this->globalRestriction
|
||||
->getScopeRestrictedAttributeList($entityType, GlobalRestriction::TYPE_FORBIDDEN);
|
||||
|
||||
$list2 = $this->globalRestriction
|
||||
->getScopeRestrictedAttributeList($entityType, GlobalRestriction::TYPE_INTERNAL);
|
||||
|
||||
$list = array_merge($list1, $list2);
|
||||
$list = array_unique($list);
|
||||
|
||||
return array_values($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getWriteRestrictedAttributeList(string $entityType): array
|
||||
{
|
||||
return $this->globalRestriction
|
||||
->getScopeRestrictedAttributeList($entityType, GlobalRestriction::TYPE_FORBIDDEN);
|
||||
}
|
||||
|
||||
public function checkLinkWrite(string $entityType, string $link): bool
|
||||
{
|
||||
$forbiddenList = $this->globalRestriction
|
||||
->getScopeRestrictedLinkList($entityType, GlobalRestriction::TYPE_FORBIDDEN);
|
||||
|
||||
return !in_array($link, $forbiddenList);
|
||||
}
|
||||
|
||||
public function checkLinkRead(string $entityType, string $link): bool
|
||||
{
|
||||
$internalList = $this->globalRestriction
|
||||
->getScopeRestrictedLinkList($entityType, GlobalRestriction::TYPE_INTERNAL);
|
||||
|
||||
$forbiddenList = $this->globalRestriction
|
||||
->getScopeRestrictedLinkList($entityType, GlobalRestriction::TYPE_FORBIDDEN);
|
||||
|
||||
return !in_array($link, $internalList) && !in_array($link, $forbiddenList);
|
||||
}
|
||||
|
||||
public function checkAttributeRead(string $entityType, string $attribute): bool
|
||||
{
|
||||
$internalList = $this->globalRestriction
|
||||
->getScopeRestrictedAttributeList($entityType, GlobalRestriction::TYPE_INTERNAL);
|
||||
|
||||
$forbiddenList = $this->globalRestriction
|
||||
->getScopeRestrictedAttributeList($entityType, GlobalRestriction::TYPE_FORBIDDEN);
|
||||
|
||||
return !in_array($attribute, $internalList) && !in_array($attribute, $forbiddenList);
|
||||
}
|
||||
|
||||
public function checkFieldRead(string $entityType, string $field): bool
|
||||
{
|
||||
$internalList = $this->globalRestriction
|
||||
->getScopeRestrictedFieldList($entityType, GlobalRestriction::TYPE_INTERNAL);
|
||||
|
||||
$forbiddenList = $this->globalRestriction
|
||||
->getScopeRestrictedFieldList($entityType, GlobalRestriction::TYPE_FORBIDDEN);
|
||||
|
||||
return !in_array($field, $internalList) && !in_array($field, $forbiddenList);
|
||||
}
|
||||
|
||||
public function checkFieldWrite(string $entityType, string $field): bool
|
||||
{
|
||||
$forbiddenList = $this->globalRestriction
|
||||
->getScopeRestrictedFieldList($entityType, GlobalRestriction::TYPE_FORBIDDEN);
|
||||
|
||||
return !in_array($field, $forbiddenList);
|
||||
}
|
||||
|
||||
public function checkAttributeWrite(string $entityType, string $attribute): bool
|
||||
{
|
||||
$forbiddenList = $this->globalRestriction
|
||||
->getScopeRestrictedAttributeList($entityType, GlobalRestriction::TYPE_FORBIDDEN);
|
||||
|
||||
return !in_array($attribute, $forbiddenList);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,9 @@
|
||||
|
||||
namespace Espo\Core\Formula;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\FieldProcessing\SpecificFieldLoader;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\ORM\Defs\AttributeParam;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
use Espo\Entities\EmailAddress;
|
||||
@@ -53,41 +55,29 @@ class AttributeFetcher
|
||||
private EntityManager $entityManager,
|
||||
private FieldUtil $fieldUtil,
|
||||
private SpecificFieldLoader $specificFieldLoader,
|
||||
private SystemRestriction $systemRestriction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
public function fetch(Entity $entity, string $attribute, bool $getFetchedAttribute = false): mixed
|
||||
{
|
||||
if (str_contains($attribute, '.')) {
|
||||
$arr = explode('.', $attribute);
|
||||
|
||||
$relationName = $arr[0];
|
||||
$link = $arr[0];
|
||||
$relatedAttribute = $arr[1] ?? null;
|
||||
|
||||
$key = $this->buildKey($entity, $relationName);
|
||||
|
||||
if (
|
||||
!array_key_exists($key, $this->relatedEntitiesCacheMap) &&
|
||||
$entity->hasRelation($relationName) &&
|
||||
!in_array(
|
||||
$entity->getRelationType($relationName),
|
||||
[Entity::MANY_MANY, Entity::HAS_MANY, Entity::HAS_CHILDREN]
|
||||
)
|
||||
) {
|
||||
$this->relatedEntitiesCacheMap[$key] = $this->entityManager
|
||||
->getRDBRepository($entity->getEntityType())
|
||||
->getRelation($entity, $relationName)
|
||||
->findOne();
|
||||
if (!$relatedAttribute) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$relatedEntity = $this->relatedEntitiesCacheMap[$key] ?? null;
|
||||
return $this->fetchRelated($entity, $link, $relatedAttribute);
|
||||
}
|
||||
|
||||
if (
|
||||
$relatedEntity instanceof Entity &&
|
||||
count($arr) > 1
|
||||
) {
|
||||
return $this->fetch($relatedEntity, $arr[1]);
|
||||
}
|
||||
|
||||
return null;
|
||||
if (!$this->systemRestriction->checkAttributeRead($entity->getEntityType(), $attribute)) {
|
||||
throw new NotAllowedUsage("Cannot read restricted attribute {$entity->getEntityType()}.$attribute.");
|
||||
}
|
||||
|
||||
if ($getFetchedAttribute) {
|
||||
@@ -184,4 +174,39 @@ class AttributeFetcher
|
||||
{
|
||||
return spl_object_hash($entity) . '-' . $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
private function fetchRelated(Entity $entity, string $link, string $attribute): mixed
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->systemRestriction->checkLinkRead($entityType, $link) ) {
|
||||
throw new NotAllowedUsage("Cannot read restricted link $entityType.$link.");
|
||||
}
|
||||
|
||||
$key = $this->buildKey($entity, $link);
|
||||
|
||||
if (
|
||||
!array_key_exists($key, $this->relatedEntitiesCacheMap) &&
|
||||
$entity->hasRelation($link) &&
|
||||
!in_array(
|
||||
$entity->getRelationType($link),
|
||||
[Entity::MANY_MANY, Entity::HAS_MANY, Entity::HAS_CHILDREN]
|
||||
)
|
||||
) {
|
||||
$this->relatedEntitiesCacheMap[$key] = $this->entityManager
|
||||
->getRelation($entity, $link)
|
||||
->findOne();
|
||||
}
|
||||
|
||||
$relatedEntity = $this->relatedEntitiesCacheMap[$key] ?? null;
|
||||
|
||||
if (!$relatedEntity instanceof Entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->fetch($relatedEntity, $attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ use Espo\Core\Formula\Exceptions\Error;
|
||||
|
||||
/**
|
||||
* A function.
|
||||
*
|
||||
* An entity passed to the constructor as of v9.4.0. But it is not an officially guaranteed contract.
|
||||
*/
|
||||
interface Func
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Formula;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\Formula\Exceptions\UnknownFunction;
|
||||
use Espo\Core\Formula\Functions\Base;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
@@ -93,7 +94,13 @@ class FunctionFactory
|
||||
$class->implementsInterface(Func::class) ||
|
||||
$class->implementsInterface(FuncVariablesAware::class)
|
||||
) {
|
||||
return $this->injectableFactory->create($className);
|
||||
$binding = new BindingContainerBuilder();
|
||||
|
||||
if ($entity) {
|
||||
$binding->bindInstance(Entity::class, $entity);
|
||||
}
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $binding->build());
|
||||
}
|
||||
|
||||
$object = $this->injectableFactory->createWith($className, [
|
||||
|
||||
@@ -29,8 +29,10 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Formula\AttributeFetcher;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use stdClass;
|
||||
|
||||
class AttributeType extends Base
|
||||
{
|
||||
@@ -49,9 +51,10 @@ class AttributeType extends Base
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws NotAllowedUsage
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(\stdClass $item)
|
||||
public function process(stdClass $item)
|
||||
{
|
||||
if (!property_exists($item, 'value')) {
|
||||
throw new Error();
|
||||
@@ -63,6 +66,7 @@ class AttributeType extends Base
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @return mixed
|
||||
* @throws NotAllowedUsage
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getAttributeValue($attribute)
|
||||
|
||||
@@ -62,8 +62,12 @@ abstract class Base
|
||||
*/
|
||||
private $variables;
|
||||
|
||||
public function __construct(string $name, Processor $processor, ?Entity $entity = null, ?stdClass $variables = null)
|
||||
{
|
||||
public function __construct(
|
||||
string $name,
|
||||
Processor $processor,
|
||||
?Entity $entity = null,
|
||||
?stdClass $variables = null,
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->processor = $processor;
|
||||
$this->entity = $entity;
|
||||
|
||||
@@ -29,26 +29,50 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\NotPassedEntity;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class AddLinkMultipleIdType extends \Espo\Core\Formula\Functions\Base
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AddLinkMultipleIdType implements Func
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
* @throws Error
|
||||
* @throws \Espo\Core\Formula\Exceptions\Error
|
||||
*/
|
||||
public function process(\stdClass $item)
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
private ?Entity $entity = null,
|
||||
) {}
|
||||
|
||||
public function process(EvaluatedArgumentList $arguments): null
|
||||
{
|
||||
if (count($item->value) < 2) {
|
||||
throw new Error("addLinkMultipleId function: Too few arguments.");
|
||||
$entity = $this->entity ?? throw new NotPassedEntity();
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$link = $this->evaluate($item->value[0]);
|
||||
$id = $this->evaluate($item->value[1]);
|
||||
if (count($arguments) < 2) {
|
||||
throw TooFewArguments::create(2);
|
||||
}
|
||||
|
||||
$link = $arguments[0];
|
||||
$id = $arguments[1];
|
||||
|
||||
if (!is_string($link)) {
|
||||
throw new Error();
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->systemRestriction->checkFieldWrite($entityType, $link)) {
|
||||
throw new NotAllowedUsage("Cannot write restricted field $entityType.$link.");
|
||||
}
|
||||
|
||||
if (is_array($id)) {
|
||||
@@ -56,17 +80,21 @@ class AddLinkMultipleIdType extends \Espo\Core\Formula\Functions\Base
|
||||
|
||||
foreach ($idList as $id) {
|
||||
if (!is_string($id)) {
|
||||
throw new Error();
|
||||
throw BadArgumentType::create(2, 'string[]');
|
||||
}
|
||||
|
||||
$this->getEntity()->addLinkMultipleId($link, $id);
|
||||
}
|
||||
} else {
|
||||
if (!is_string($id)) {
|
||||
return;
|
||||
$entity->addLinkMultipleId($link, $id);
|
||||
}
|
||||
|
||||
$this->getEntity()->addLinkMultipleId($link, $id);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entity->addLinkMultipleId($link, $id);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,15 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
|
||||
class AttributeFetchedType extends AttributeType
|
||||
{
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getAttributeValue($attribute)
|
||||
{
|
||||
return $this->attributeFetcher->fetch($this->getEntity(), $attribute, true);
|
||||
|
||||
@@ -29,18 +29,23 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
|
||||
class AttributeType extends \Espo\Core\Formula\Functions\AttributeType
|
||||
{
|
||||
public function process(\stdClass $item)
|
||||
{
|
||||
if (count($item->value) < 1) {
|
||||
throw new Error("attribute function: Too few arguments.");
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
$attribute = $this->evaluate($item->value[0]);
|
||||
|
||||
if (!is_string($attribute)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
return $this->getAttributeValue($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,31 +29,52 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Formula\ArgumentList;
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\NotPassedEntity;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ClearAttributeType extends BaseFunction
|
||||
class ClearAttributeType implements Func
|
||||
{
|
||||
public function process(ArgumentList $args)
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
private ?Entity $entity = null,
|
||||
) {}
|
||||
|
||||
public function process(EvaluatedArgumentList $arguments): null
|
||||
{
|
||||
if (count($args) < 1) {
|
||||
$entity = $this->entity ?? throw new NotPassedEntity();
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
if (count($arguments) < 1) {
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
$args = $this->evaluate($args);
|
||||
|
||||
$attribute = $args[0];
|
||||
$attribute = $arguments[0];
|
||||
|
||||
if (!is_string($attribute)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
$this->getEntity()->clear($attribute);
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->systemRestriction->checkAttributeWrite($entityType, $attribute)) {
|
||||
throw new NotAllowedUsage("Cannot write restricted attribute $entityType.$attribute.");
|
||||
}
|
||||
|
||||
$entity->clear($attribute);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -29,67 +29,78 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
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\Error;
|
||||
use Espo\Core\Formula\Functions\Base;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\NotPassedEntity;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\Core\Di;
|
||||
use stdClass;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class CountRelatedType extends Base implements
|
||||
Di\EntityManagerAware,
|
||||
Di\InjectableFactoryAware,
|
||||
Di\UserAware
|
||||
class CountRelatedType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
use Di\UserSetter;
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
private EntityManager $entityManager,
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private FindQueryUtil $findQueryUtil,
|
||||
private ?Entity $entity = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(stdClass $item)
|
||||
public function process(EvaluatedArgumentList $arguments): int
|
||||
{
|
||||
if (count($item->value) < 1) {
|
||||
throw new Error("countRelated: roo few arguments.");
|
||||
$entity = $this->entity ?? throw new NotPassedEntity();
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$link = $this->evaluate($item->value[0]);
|
||||
if (count($arguments) < 1) {
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
if (empty($link)) {
|
||||
throw new Error("countRelated: no link passed.");
|
||||
$link = $arguments[0];
|
||||
|
||||
if (!is_string($link)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->systemRestriction->checkLinkRead($entityType, $link)) {
|
||||
throw new NotAllowedUsage("Cannot read restricted field $entityType.$link.");
|
||||
}
|
||||
|
||||
$filter = null;
|
||||
|
||||
if (count($item->value) > 1) {
|
||||
$filter = $this->evaluate($item->value[1]);
|
||||
if (count($arguments) > 1) {
|
||||
$filter = $arguments[1];
|
||||
}
|
||||
|
||||
$entity = $this->getEntity();
|
||||
|
||||
$entityManager = $this->entityManager;
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, RelationParam::ENTITY);
|
||||
|
||||
if (empty($foreignEntityType)) {
|
||||
throw new Error();
|
||||
throw new Error("Not supported link $link.");
|
||||
}
|
||||
|
||||
$builder = $this->injectableFactory->create(SelectBuilderFactory::class)
|
||||
$builder = $this->selectBuilderFactory
|
||||
->create()
|
||||
->forUser($this->user)
|
||||
->from($foreignEntityType);
|
||||
|
||||
if ($filter) {
|
||||
(new FindQueryUtil())->applyFilter($builder, $filter, 2);
|
||||
$this->findQueryUtil->applyFilter($builder, $filter, 2);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -98,7 +109,8 @@ class CountRelatedType extends Base implements
|
||||
throw new Error($e->getMessage());
|
||||
}
|
||||
|
||||
return $entityManager->getRDBRepository($entity->getEntityType())
|
||||
return $this->entityManager
|
||||
->getRDBRepository($entity->getEntityType())
|
||||
->getRelation($entity, $link)
|
||||
->clone($query)
|
||||
->count();
|
||||
|
||||
@@ -29,44 +29,67 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\NotPassedEntity;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
class GetLinkColumnType extends \Espo\Core\Formula\Functions\Base implements
|
||||
Di\EntityManagerAware
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class GetLinkColumnType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
private EntityManager $entityManager,
|
||||
private ?Entity $entity = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws Error
|
||||
* @throws \Espo\Core\Formula\Exceptions\Error
|
||||
*/
|
||||
public function process(\stdClass $item)
|
||||
public function process(EvaluatedArgumentList $arguments): mixed
|
||||
{
|
||||
$args = $item->value ?? [];
|
||||
$entity = $this->entity ?? throw new NotPassedEntity();
|
||||
|
||||
if (count($args) < 3) {
|
||||
throw new Error("Formula: entity\\isRelated: no argument.");
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$link = $this->evaluate($args[0]);
|
||||
$id = $this->evaluate($args[1]);
|
||||
$column = $this->evaluate($args[2]);
|
||||
if (count($arguments) < 3) {
|
||||
throw TooFewArguments::create(3);
|
||||
}
|
||||
|
||||
$link = $arguments[0];
|
||||
$id = $arguments[1];
|
||||
$column = $arguments[2];
|
||||
|
||||
if (!is_string($link)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($id)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($column)) {
|
||||
throw BadArgumentType::create(3, 'string');
|
||||
}
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->systemRestriction->checkFieldRead($entityType, $link)) {
|
||||
throw new NotAllowedUsage("Cannot read restricted field $entityType.$link.");
|
||||
}
|
||||
|
||||
$entityType = $this->getEntity()->getEntityType();
|
||||
$repository = $this->entityManager->getRDBRepository($entityType);
|
||||
|
||||
return $repository
|
||||
->getRelation($this->getEntity(), $link)
|
||||
->getRelation($entity, $link)
|
||||
->getColumnById($id, $column);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,32 +29,56 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\NotPassedEntity;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class HasLinkMultipleIdType extends \Espo\Core\Formula\Functions\Base
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class HasLinkMultipleIdType implements Func
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Error
|
||||
* @throws \Espo\Core\Formula\Exceptions\Error
|
||||
*/
|
||||
public function process(\stdClass $item)
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
private ?Entity $entity = null,
|
||||
) {}
|
||||
|
||||
public function process(EvaluatedArgumentList $arguments): bool
|
||||
{
|
||||
if (count($item->value) < 2) {
|
||||
throw new Error("hasLinkMultipleId: too few arguments.");
|
||||
$entity = $this->entity ?? throw new NotPassedEntity();
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$link = $this->evaluate($item->value[0]);
|
||||
$id = $this->evaluate($item->value[1]);
|
||||
if (count($arguments) < 2) {
|
||||
throw TooFewArguments::create(2);
|
||||
}
|
||||
|
||||
$link = $arguments[0];
|
||||
$id = $arguments[1];
|
||||
|
||||
if (!is_string($link)) {
|
||||
throw new Error();
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($id)) {
|
||||
throw new Error();
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
return $this->getEntity()->hasLinkMultipleId($link, $id);
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->systemRestriction->checkFieldRead($entityType, $link)) {
|
||||
throw new NotAllowedUsage("Cannot read restricted field $entityType.$link.");
|
||||
}
|
||||
|
||||
return $entity->hasLinkMultipleId($link, $id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,23 +29,29 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Functions\Base;
|
||||
|
||||
class IsAttributeChangedType extends \Espo\Core\Formula\Functions\Base
|
||||
class IsAttributeChangedType extends Base
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Error
|
||||
* @throws \Espo\Core\Formula\Exceptions\Error
|
||||
*/
|
||||
public function process(\stdClass $item)
|
||||
{
|
||||
if (count($item->value) < 1) {
|
||||
throw new Error("isAttributeChanged: too few arguments.");
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
$attribute = $this->evaluate($item->value[0]);
|
||||
|
||||
if (!is_string($attribute)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
return $this->check($attribute);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,14 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
|
||||
class IsAttributeNotChangedType extends IsAttributeChangedType
|
||||
{
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @return bool
|
||||
* @throws \Espo\Core\Exceptions\Error
|
||||
* @throws Error
|
||||
*/
|
||||
protected function check($attribute)
|
||||
{
|
||||
|
||||
@@ -29,11 +29,13 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
|
||||
class IsNewType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \Espo\Core\Exceptions\Error
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(\stdClass $item)
|
||||
{
|
||||
|
||||
@@ -29,32 +29,60 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Functions\Base;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\NotPassedEntity;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
class IsRelatedType extends Base implements
|
||||
Di\EntityManagerAware
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class IsRelatedType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
private EntityManager $entityManager,
|
||||
private ?Entity $entity = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(\stdClass $item)
|
||||
public function process(EvaluatedArgumentList $arguments): bool
|
||||
{
|
||||
if (count($item->value) < 2) {
|
||||
throw new Error("isRelated: roo few arguments.");
|
||||
$entity = $this->entity ?? throw new NotPassedEntity();
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$link = $this->evaluate($item->value[0]);
|
||||
$id = $this->evaluate($item->value[1]);
|
||||
if (count($arguments) < 2) {
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
$link = $arguments[0];
|
||||
$id = $arguments[1];
|
||||
|
||||
if (!is_string($link)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($id)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->systemRestriction->checkLinkRead($entityType, $link)) {
|
||||
throw new NotAllowedUsage("Cannot read restricted field $entityType.$link.");
|
||||
}
|
||||
|
||||
return $this->entityManager
|
||||
->getRDBRepository($this->getEntity()->getEntityType())
|
||||
->getRelation($this->getEntity(), $link)
|
||||
->getRelation($entity, $link)
|
||||
->isRelatedById($id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,32 +29,58 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\NotPassedEntity;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class RemoveLinkMultipleIdType extends \Espo\Core\Formula\Functions\Base
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class RemoveLinkMultipleIdType implements Func
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
* @throws \Espo\Core\Formula\Exceptions\Error
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(\stdClass $item)
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
private ?Entity $entity = null,
|
||||
) {}
|
||||
|
||||
public function process(EvaluatedArgumentList $arguments): null
|
||||
{
|
||||
if (count($item->value) < 2) {
|
||||
throw new Error("removeLinkMultipleId: roo few arguments.");
|
||||
$entity = $this->entity ?? throw new NotPassedEntity();
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$link = $this->evaluate($item->value[0]);
|
||||
$id = $this->evaluate($item->value[1]);
|
||||
if (count($arguments) < 2) {
|
||||
throw TooFewArguments::create(2);
|
||||
}
|
||||
|
||||
$link = $arguments[0];
|
||||
$id = $arguments[1];
|
||||
|
||||
if (!is_string($link)) {
|
||||
throw new Error();
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($id)) {
|
||||
throw new Error();
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
$this->getEntity()->removeLinkMultipleId($link, $id);
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->systemRestriction->checkFieldWrite($entityType, $link)) {
|
||||
throw new NotAllowedUsage("Cannot write restricted field $entityType.$link.");
|
||||
}
|
||||
|
||||
$entity->removeLinkMultipleId($link, $id);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,30 +29,64 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Formula\ArgumentList;
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
use Espo\Core\ORM\Entity;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\NotPassedEntity;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class SetLinkMultipleColumnType extends BaseFunction
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class SetLinkMultipleColumnType implements Func
|
||||
{
|
||||
public function process(ArgumentList $args)
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
private ?Entity $entity = null,
|
||||
) {}
|
||||
|
||||
public function process(EvaluatedArgumentList $arguments): null
|
||||
{
|
||||
if (count($args) < 4) {
|
||||
$this->throwTooFewArguments(4);
|
||||
$entity = $this->entity ?? throw new NotPassedEntity();
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$link = $this->evaluate($args[0]);
|
||||
$id = $this->evaluate($args[1]);
|
||||
$column = $this->evaluate($args[2]);
|
||||
$value = $this->evaluate($args[3]);
|
||||
if (count($arguments) < 4) {
|
||||
throw TooFewArguments::create(4);
|
||||
}
|
||||
|
||||
$entity = $this->getEntity();
|
||||
$link = $arguments[0];
|
||||
$id = $arguments[1];
|
||||
$column = $arguments[2];
|
||||
$value = $arguments[3];
|
||||
|
||||
if (!$entity instanceof Entity) {
|
||||
throw new Error();
|
||||
if (!is_string($link)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($id)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($column)) {
|
||||
throw BadArgumentType::create(3, 'string');
|
||||
}
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->systemRestriction->checkFieldWrite($entityType, $link)) {
|
||||
throw new NotAllowedUsage("Cannot write restricted field $entityType.$link.");
|
||||
}
|
||||
|
||||
$entity->setLinkMultipleColumn($link, $column, $id, $value);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,82 +29,96 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\EntityGroup;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
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\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\NotPassedEntity;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use stdClass;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class SumRelatedType extends Base implements
|
||||
Di\EntityManagerAware,
|
||||
Di\InjectableFactoryAware,
|
||||
Di\UserAware
|
||||
class SumRelatedType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
use Di\UserSetter;
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
private EntityManager $entityManager,
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private FindQueryUtil $findQueryUtil,
|
||||
private ?Entity $entity = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(stdClass $item)
|
||||
public function process(EvaluatedArgumentList $arguments): int|float
|
||||
{
|
||||
if (count($item->value) < 2) {
|
||||
throw new Error("sumRelated: Too few arguments.");
|
||||
$entity = $this->entity ?? throw new NotPassedEntity();
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$link = $this->evaluate($item->value[0]);
|
||||
|
||||
if (empty($link)) {
|
||||
throw new Error("No link passed to sumRelated function.");
|
||||
if (count($arguments) < 2) {
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
$field = $this->evaluate($item->value[1]);
|
||||
$link = $arguments[0];
|
||||
$field = $arguments[1];
|
||||
|
||||
if (empty($field)) {
|
||||
throw new Error("No field passed to sumRelated function.");
|
||||
if (!is_string($link)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($field)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
$filter = null;
|
||||
|
||||
if (count($item->value) > 2) {
|
||||
$filter = $this->evaluate($item->value[2]);
|
||||
if (count($arguments) > 2) {
|
||||
$filter = $arguments[2];
|
||||
}
|
||||
|
||||
$entity = $this->getEntity();
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$entityManager = $this->entityManager;
|
||||
if (!$this->systemRestriction->checkLinkRead($entityType, $link)) {
|
||||
throw new NotAllowedUsage("Cannot read restricted field $entityType.$link.");
|
||||
}
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, RelationParam::ENTITY);
|
||||
|
||||
if (empty($foreignEntityType)) {
|
||||
throw new Error();
|
||||
if (!$foreignEntityType) {
|
||||
throw new Error("Not supported link '$link'.");
|
||||
}
|
||||
|
||||
if (!$this->systemRestriction->checkLinkRead($foreignEntityType, $field)) {
|
||||
throw new NotAllowedUsage("Cannot read restricted field $foreignEntityType.$field.");
|
||||
}
|
||||
|
||||
$foreignLink = $entity->getRelationParam($link, RelationParam::FOREIGN);
|
||||
$foreignLinkAlias = $foreignLink . 'SumRelated';
|
||||
|
||||
if (empty($foreignLink)) {
|
||||
throw new Error("No foreign link for link {$link}.");
|
||||
throw new Error("No foreign link for link $link.");
|
||||
}
|
||||
|
||||
$builder = $this->injectableFactory->create(SelectBuilderFactory::class)
|
||||
$builder = $this->selectBuilderFactory
|
||||
->create()
|
||||
->forUser($this->user)
|
||||
->from($foreignEntityType);
|
||||
|
||||
if ($filter) {
|
||||
(new FindQueryUtil())->applyFilter($builder, $filter, 3);
|
||||
$this->findQueryUtil->applyFilter($builder, $filter, 3);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -156,7 +170,7 @@ class SumRelatedType extends Base implements
|
||||
|
||||
$queryBuilder->group($foreignLinkAlias . '.id');
|
||||
|
||||
$sth = $entityManager->getQueryExecutor()->execute($queryBuilder->build());
|
||||
$sth = $this->entityManager->getQueryExecutor()->execute($queryBuilder->build());
|
||||
|
||||
$rowList = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
|
||||
use Espo\Core\Di;
|
||||
use stdClass;
|
||||
|
||||
class AttributeType extends \Espo\Core\Formula\Functions\AttributeType implements
|
||||
Di\EntityManagerAware
|
||||
@@ -41,7 +43,7 @@ class AttributeType extends \Espo\Core\Formula\Functions\AttributeType implement
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(\stdClass $item)
|
||||
public function process(stdClass $item)
|
||||
{
|
||||
if (count($item->value) < 3) {
|
||||
throw new Error("record\\attribute: too few arguments.");
|
||||
@@ -51,16 +53,16 @@ class AttributeType extends \Espo\Core\Formula\Functions\AttributeType implement
|
||||
$id = $this->evaluate($item->value[1]);
|
||||
$attribute = $this->evaluate($item->value[2]);
|
||||
|
||||
if (!$entityType) {
|
||||
throw new Error("Formula record\\attribute: Empty entityType.");
|
||||
if (!is_string($entityType)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
return null;
|
||||
if (!is_string($id)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
if (!$attribute) {
|
||||
throw new Error("Formula record\\attribute: Empty attribute.");
|
||||
if (!is_string($attribute)) {
|
||||
throw BadArgumentType::create(3, 'string');
|
||||
}
|
||||
|
||||
$entity = $this->entityManager->getEntityById($entityType, $id);
|
||||
|
||||
@@ -31,48 +31,52 @@ namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
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\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil;
|
||||
use Espo\Core\Di;
|
||||
use Espo\Core\Select\Primary\Filters\All;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class CountType extends BaseFunction implements
|
||||
Di\EntityManagerAware,
|
||||
Di\InjectableFactoryAware,
|
||||
Di\UserAware
|
||||
class CountType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
use Di\UserSetter;
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private FindQueryUtil $findQueryUtil,
|
||||
) {}
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
public function process(EvaluatedArgumentList $arguments): int
|
||||
{
|
||||
if (count($args) < 1) {
|
||||
$this->throwTooFewArguments(1);
|
||||
if (count($arguments) < 1) {
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
$entityType = $this->evaluate($args[0]);
|
||||
$entityType = $arguments[0];
|
||||
|
||||
if (count($args) < 3) {
|
||||
if (!is_string($entityType)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (count($arguments) < 3) {
|
||||
$filter = null;
|
||||
|
||||
if (count($args) === 2) {
|
||||
$filter = $this->evaluate($args[1]);
|
||||
if (count($arguments) === 2) {
|
||||
$filter = $arguments[1];
|
||||
}
|
||||
|
||||
$builder = $this->injectableFactory->create(SelectBuilderFactory::class)
|
||||
$builder = $this->selectBuilderFactory
|
||||
->create()
|
||||
->forUser($this->user)
|
||||
->withPrimaryFilter(All::NAME)
|
||||
->from($entityType);
|
||||
|
||||
(new FindQueryUtil())->applyFilter($builder, $filter, 2);
|
||||
$this->findQueryUtil->applyFilter($builder, $filter, 2);
|
||||
|
||||
try {
|
||||
return $this->entityManager
|
||||
@@ -80,7 +84,7 @@ class CountType extends BaseFunction implements
|
||||
->clone($builder->build())
|
||||
->count();
|
||||
} catch (BadRequest|Forbidden $e) {
|
||||
throw new Error($e->getMessage(), 0, $e);
|
||||
throw new NotAllowedUsage($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +92,11 @@ class CountType extends BaseFunction implements
|
||||
|
||||
$i = 1;
|
||||
|
||||
while ($i < count($args) - 1) {
|
||||
$key = $this->evaluate($args[$i]);
|
||||
$value = $this->evaluate($args[$i + 1]);
|
||||
while ($i < count($arguments) - 1) {
|
||||
$key = $arguments[$i];
|
||||
$value = $arguments[$i + 1];
|
||||
|
||||
$this->findQueryUtil->assertWhereClauseKeyValid($entityType, $key);
|
||||
|
||||
$whereClause[] = [$key => $value];
|
||||
|
||||
|
||||
@@ -29,46 +29,52 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Formula\ArgumentList;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
use Espo\Core\Di;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\Formula\Utils\EntityUtil;
|
||||
use RuntimeException;
|
||||
use Espo\ORM\EntityManager;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class CreateType extends BaseFunction implements
|
||||
Di\EntityManagerAware
|
||||
class CreateType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private EntityUtil $entityUtil,
|
||||
) {}
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
public function process(EvaluatedArgumentList $arguments): ?string
|
||||
{
|
||||
if (count($args) < 1) {
|
||||
$this->throwTooFewArguments(1);
|
||||
if (count($arguments) < 1) {
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
$args = $this->evaluate($args);
|
||||
|
||||
if (!is_array($args)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$entityType = $args[0];
|
||||
$entityType = $arguments[0];
|
||||
|
||||
if (!is_string($entityType)) {
|
||||
$this->throwBadArgumentType(1, 'string');
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
$data = $this->getData($args, $entityType);
|
||||
$data = $this->getData($arguments, $entityType);
|
||||
|
||||
$notAllowedAttributes = array_intersect(
|
||||
array_keys($data),
|
||||
$this->entityUtil->getWriteRestrictedAttributeList($entityType),
|
||||
);
|
||||
|
||||
if ($notAllowedAttributes) {
|
||||
throw new NotAllowedUsage("Cannot write $entityType.$notAllowedAttributes[0].");
|
||||
}
|
||||
|
||||
$entity = $this->entityManager->getNewEntity($entityType);
|
||||
$entity->setMultiple($data);
|
||||
|
||||
EntityUtil::checkUpdateAccess($entity);
|
||||
$this->entityUtil->assertUpdateAccess($entity);
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
|
||||
@@ -76,11 +82,10 @@ class CreateType extends BaseFunction implements
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $args
|
||||
* @return array<string, mixed>
|
||||
* @throws BadArgumentType
|
||||
*/
|
||||
private function getData(array $args, mixed $entityType): array
|
||||
private function getData(EvaluatedArgumentList $args, mixed $entityType): array
|
||||
{
|
||||
if (count($args) >= 2 && $args[1] instanceof stdClass) {
|
||||
return get_object_vars($args[1]);
|
||||
@@ -94,7 +99,7 @@ class CreateType extends BaseFunction implements
|
||||
$attribute = $args[$i];
|
||||
|
||||
if (!is_string($entityType)) {
|
||||
$this->throwBadArgumentType($i + 1, 'string');
|
||||
throw BadArgumentType::create($i + 1, 'string');
|
||||
}
|
||||
|
||||
/** @var string $attribute */
|
||||
|
||||
@@ -36,11 +36,17 @@ use Espo\Core\Formula\Func;
|
||||
use Espo\Core\Formula\Utils\EntityUtil;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class DeleteType implements Func
|
||||
{
|
||||
public function __construct(private EntityManager $entityManager) {}
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private EntityUtil $entityUtil,
|
||||
) {}
|
||||
|
||||
public function process(EvaluatedArgumentList $arguments): mixed
|
||||
public function process(EvaluatedArgumentList $arguments): null
|
||||
{
|
||||
if (count($arguments) < 2) {
|
||||
throw TooFewArguments::create(2);
|
||||
@@ -63,7 +69,7 @@ class DeleteType implements Func
|
||||
return null;
|
||||
}
|
||||
|
||||
EntityUtil::checkRemoveAccess($entity);
|
||||
$this->entityUtil->assertRemoveAccess($entity);
|
||||
|
||||
$this->entityManager->removeEntity($entity);
|
||||
|
||||
|
||||
@@ -31,48 +31,53 @@ namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
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\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil;
|
||||
use Espo\Core\Select\Primary\Filters\All;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ExistsType extends BaseFunction implements
|
||||
Di\EntityManagerAware,
|
||||
Di\InjectableFactoryAware,
|
||||
Di\UserAware
|
||||
class ExistsType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
use Di\UserSetter;
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private FindQueryUtil $findQueryUtil,
|
||||
) {}
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
public function process(EvaluatedArgumentList $arguments): bool
|
||||
{
|
||||
if (count($args) < 1) {
|
||||
$this->throwTooFewArguments(1);
|
||||
if (count($arguments) < 1) {
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
$entityType = $this->evaluate($args[0]);
|
||||
$entityType = $arguments[0];
|
||||
|
||||
if (count($args) <= 2) {
|
||||
if (!is_string($entityType)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (count($arguments) <= 2) {
|
||||
$filter = null;
|
||||
|
||||
if (count($args) === 2) {
|
||||
$filter = $this->evaluate($args[1]);
|
||||
if (count($arguments) === 2) {
|
||||
$filter = $arguments[1];
|
||||
}
|
||||
|
||||
$builder = $this->injectableFactory->create(SelectBuilderFactory::class)
|
||||
$builder = $this->selectBuilderFactory
|
||||
->create()
|
||||
->forUser($this->user)
|
||||
->withPrimaryFilter(All::NAME)
|
||||
->from($entityType);
|
||||
|
||||
(new FindQueryUtil())->applyFilter($builder, $filter, 2);
|
||||
$this->findQueryUtil->applyFilter($builder, $filter, 2);
|
||||
|
||||
try {
|
||||
return (bool) $this->entityManager
|
||||
@@ -80,7 +85,7 @@ class ExistsType extends BaseFunction implements
|
||||
->clone($builder->build())
|
||||
->findOne();
|
||||
} catch (BadRequest|Forbidden $e) {
|
||||
throw new Error($e->getMessage(), 0, $e);
|
||||
throw new NotAllowedUsage($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +93,11 @@ class ExistsType extends BaseFunction implements
|
||||
|
||||
$i = 1;
|
||||
|
||||
while ($i < count($args) - 1) {
|
||||
$key = $this->evaluate($args[$i]);
|
||||
$value = $this->evaluate($args[$i + 1]);
|
||||
while ($i < count($arguments) - 1) {
|
||||
$key = $arguments[$i];
|
||||
$value = $arguments[$i + 1];
|
||||
|
||||
$this->findQueryUtil->assertWhereClauseKeyValid($entityType, $key);
|
||||
|
||||
$whereClause[] = [$key => $value];
|
||||
|
||||
@@ -99,6 +106,7 @@ class ExistsType extends BaseFunction implements
|
||||
|
||||
return (bool) $this->entityManager
|
||||
->getRDBRepository($entityType)
|
||||
->select([Attribute::ID])
|
||||
->where($whereClause)
|
||||
->findOne();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
@@ -41,7 +42,10 @@ use stdClass;
|
||||
|
||||
class FetchType implements Func
|
||||
{
|
||||
public function __construct(private EntityManager $entityManager) {}
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private SystemRestriction $systemRestriction,
|
||||
) {}
|
||||
|
||||
public function process(EvaluatedArgumentList $arguments): ?stdClass
|
||||
{
|
||||
@@ -68,6 +72,10 @@ class FetchType implements Func
|
||||
|
||||
$this->load($entity);
|
||||
|
||||
foreach ($this->systemRestriction->getReadRestrictedAttributeList($entityType) as $attribute) {
|
||||
$entity->clear($attribute);
|
||||
}
|
||||
|
||||
return $entity->getValueMap();
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,7 @@ 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\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil;
|
||||
@@ -43,13 +42,16 @@ use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class FindManyType implements Func
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private SelectBuilderFactory $selectBuilderFactory
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private FindQueryUtil $findQueryUtil,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -58,14 +60,14 @@ class FindManyType implements Func
|
||||
*/
|
||||
public function process(EvaluatedArgumentList $arguments): array
|
||||
{
|
||||
if (count($arguments) < 4) {
|
||||
throw TooFewArguments::create(4);
|
||||
if (count($arguments) < 2) {
|
||||
throw TooFewArguments::create(2);
|
||||
}
|
||||
|
||||
$entityType = $arguments[0];
|
||||
$limit = $arguments[1];
|
||||
$orderBy = $arguments[2];
|
||||
$order = $arguments[3] ?? Order::ASC;
|
||||
$orderBy = $arguments[2] ?? null;
|
||||
$order = $arguments[3] ?? null;
|
||||
|
||||
if (!is_string($entityType)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
@@ -79,16 +81,8 @@ class FindManyType implements Func
|
||||
throw BadArgumentType::create(3, 'string|null');
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
if ($order !== null && !is_bool($order) && !is_string($order)) {
|
||||
throw BadArgumentType::create(4, 'string|bool|null');
|
||||
}
|
||||
|
||||
$builder = $this->selectBuilderFactory
|
||||
@@ -96,6 +90,8 @@ class FindManyType implements Func
|
||||
->withPrimaryFilter(All::NAME)
|
||||
->from($entityType);
|
||||
|
||||
$this->findQueryUtil->applyOrder($builder, $orderBy, $order, 4);
|
||||
|
||||
$whereClause = [];
|
||||
|
||||
if (count($arguments) <= 5) {
|
||||
@@ -105,7 +101,7 @@ class FindManyType implements Func
|
||||
$filter = $arguments[4];
|
||||
}
|
||||
|
||||
(new FindQueryUtil())->applyFilter($builder, $filter, 5);
|
||||
$this->findQueryUtil->applyFilter($builder, $filter, 5);
|
||||
} else {
|
||||
$i = 4;
|
||||
|
||||
@@ -113,6 +109,8 @@ class FindManyType implements Func
|
||||
$key = $arguments[$i];
|
||||
$value = $arguments[$i + 1];
|
||||
|
||||
$this->findQueryUtil->assertWhereClauseKeyValid($entityType, $key);
|
||||
|
||||
$whereClause[] = [$key => $value];
|
||||
|
||||
$i = $i + 2;
|
||||
@@ -122,17 +120,13 @@ class FindManyType implements Func
|
||||
try {
|
||||
$queryBuilder = $builder->buildQueryBuilder();
|
||||
} catch (BadRequest|Forbidden $e) {
|
||||
throw new FormulaError($e->getMessage(), $e->getCode(), $e);
|
||||
throw new NotAllowedUsage($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if (!empty($whereClause)) {
|
||||
$queryBuilder->where($whereClause);
|
||||
}
|
||||
|
||||
if ($orderBy) {
|
||||
$queryBuilder->order($orderBy, $order);
|
||||
}
|
||||
|
||||
$queryBuilder
|
||||
->select([Attribute::ID])
|
||||
->limit(0, $limit);
|
||||
|
||||
@@ -31,60 +31,75 @@ namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
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\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil;
|
||||
use Espo\Core\Select\Primary\Filters\All;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class FindOneType extends BaseFunction implements
|
||||
Di\EntityManagerAware,
|
||||
Di\InjectableFactoryAware,
|
||||
Di\UserAware
|
||||
class FindOneType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
use Di\UserSetter;
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private FindQueryUtil $findQueryUtil,
|
||||
) {}
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
public function process(EvaluatedArgumentList $arguments): ?string
|
||||
{
|
||||
if (count($args) < 3) {
|
||||
$this->throwTooFewArguments(3);
|
||||
if (count($arguments) < 1) {
|
||||
throw TooFewArguments::create(1);
|
||||
}
|
||||
|
||||
$entityType = $this->evaluate($args[0]);
|
||||
$orderBy = $this->evaluate($args[1]);
|
||||
$order = $this->evaluate($args[2]) ?? Order::ASC;
|
||||
$entityType = $arguments[0];
|
||||
$orderBy = $arguments[1] ?? null;
|
||||
$order = $arguments[2] ?? null;
|
||||
|
||||
$builder = $this->injectableFactory->create(SelectBuilderFactory::class)
|
||||
if (!is_string($entityType)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if ($orderBy !== null && !is_string($orderBy)) {
|
||||
throw BadArgumentType::create(2, 'string|null');
|
||||
}
|
||||
|
||||
if ($order !== null && !is_bool($order) && !is_string($order)) {
|
||||
throw BadArgumentType::create(3, 'string|bool|null');
|
||||
}
|
||||
|
||||
$builder = $this->selectBuilderFactory
|
||||
->create()
|
||||
->forUser($this->user)
|
||||
->withPrimaryFilter(All::NAME)
|
||||
->from($entityType);
|
||||
|
||||
$this->findQueryUtil->applyOrder($builder, $orderBy, $order, 3);
|
||||
|
||||
$whereClause = [];
|
||||
|
||||
if (count($args) <= 4) {
|
||||
if (count($arguments) <= 4) {
|
||||
$filter = null;
|
||||
|
||||
if (count($args) === 4) {
|
||||
$filter = $this->evaluate($args[3]);
|
||||
if (count($arguments) === 4) {
|
||||
$filter = $arguments[3];
|
||||
}
|
||||
|
||||
(new FindQueryUtil())->applyFilter($builder, $filter, 4);
|
||||
$this->findQueryUtil->applyFilter($builder, $filter, 4);
|
||||
} else {
|
||||
$i = 3;
|
||||
|
||||
while ($i < count($args) - 1) {
|
||||
$key = $this->evaluate($args[$i]);
|
||||
$value = $this->evaluate($args[$i + 1]);
|
||||
while ($i < count($arguments) - 1) {
|
||||
$key = $arguments[$i];
|
||||
$value = $arguments[$i + 1];
|
||||
|
||||
$this->findQueryUtil->assertWhereClauseKeyValid($entityType, $key);
|
||||
|
||||
$whereClause[] = [$key => $value];
|
||||
|
||||
@@ -95,17 +110,13 @@ class FindOneType extends BaseFunction implements
|
||||
try {
|
||||
$queryBuilder = $builder->buildQueryBuilder();
|
||||
} catch (BadRequest|Forbidden $e) {
|
||||
throw new FormulaError($e->getMessage(), $e->getCode(), $e);
|
||||
throw new NotAllowedUsage($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if (!empty($whereClause)) {
|
||||
$queryBuilder->where($whereClause);
|
||||
}
|
||||
|
||||
if ($orderBy) {
|
||||
$queryBuilder->order($orderBy, $order);
|
||||
}
|
||||
|
||||
$queryBuilder->select([Attribute::ID]);
|
||||
|
||||
$entity = $this->entityManager
|
||||
@@ -113,10 +124,6 @@ class FindOneType extends BaseFunction implements
|
||||
->clone($queryBuilder->build())
|
||||
->findOne();
|
||||
|
||||
if ($entity) {
|
||||
return $entity->getId();
|
||||
}
|
||||
|
||||
return null;
|
||||
return $entity?->getId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,117 +29,105 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
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\Error;
|
||||
use Espo\Core\Formula\Exceptions\ExecutionException;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
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;
|
||||
use Espo\Core\Di;
|
||||
use Espo\Core\Select\Helpers\RandomStringGenerator;
|
||||
use Espo\Core\Select\Primary\Filters\All;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class FindRelatedManyType extends BaseFunction implements
|
||||
Di\EntityManagerAware,
|
||||
Di\MetadataAware,
|
||||
Di\InjectableFactoryAware,
|
||||
Di\UserAware
|
||||
class FindRelatedManyType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\MetadataSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
use Di\UserSetter;
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private FindQueryUtil $findQueryUtil,
|
||||
private RandomStringGenerator $randomStringGenerator,
|
||||
private SystemRestriction $systemRestriction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @throws Error
|
||||
* @throws TooFewArguments
|
||||
* @throws BadArgumentType
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
public function process(ArgumentList $args)
|
||||
public function process(EvaluatedArgumentList $arguments): array
|
||||
{
|
||||
$args = $this->evaluate($args);
|
||||
|
||||
if (count($args) < 4) {
|
||||
$this->throwTooFewArguments(4);
|
||||
if (count($arguments) < 4) {
|
||||
throw TooFewArguments::create(4);
|
||||
}
|
||||
|
||||
$entityManager = $this->entityManager;
|
||||
|
||||
$entityType = $args[0];
|
||||
$id = $args[1];
|
||||
$link = $args[2];
|
||||
$limit = $args[3];
|
||||
$entityType = $arguments[0];
|
||||
$id = $arguments[1];
|
||||
$link = $arguments[2];
|
||||
$limit = $arguments[3];
|
||||
|
||||
$orderBy = null;
|
||||
$order = null;
|
||||
|
||||
if (count($args) > 4) {
|
||||
$orderBy = $args[4];
|
||||
if (count($arguments) > 4) {
|
||||
$orderBy = $arguments[4];
|
||||
}
|
||||
|
||||
if (count($args) > 5) {
|
||||
$order = $args[5];
|
||||
if (count($arguments) > 5) {
|
||||
$order = $arguments[5];
|
||||
}
|
||||
|
||||
if (!$entityType || !is_string($entityType)) {
|
||||
$this->throwBadArgumentType(1, 'string');
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
$this->log("Empty ID.");
|
||||
|
||||
return [];
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($id)) {
|
||||
$this->throwBadArgumentType(2, 'string');
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
if (!$link || !is_string($link)) {
|
||||
$this->throwBadArgumentType(3, 'string');
|
||||
throw BadArgumentType::create(3, 'string');
|
||||
}
|
||||
|
||||
if (!is_int($limit)) {
|
||||
$this->throwBadArgumentType(4, 'string');
|
||||
throw BadArgumentType::create(4, 'int');
|
||||
}
|
||||
|
||||
if ($orderBy !== null && !is_string($orderBy)) {
|
||||
throw BadArgumentType::create(5, 'string|null');
|
||||
}
|
||||
|
||||
if ($order !== null && !is_string($order) && !is_bool($order)) {
|
||||
throw BadArgumentType::create(6, 'string|bool|null');
|
||||
}
|
||||
|
||||
$this->assertLinkRead($entityType, $link);
|
||||
|
||||
$entity = $entityManager->getEntityById($entityType, $id);
|
||||
|
||||
if (!$entity) {
|
||||
$this->log("record\\findRelatedMany: Entity $entityType $id not found.", 'notice');
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$metadata = $this->metadata;
|
||||
|
||||
if (!$orderBy) {
|
||||
$orderBy = $metadata->get(['entityDefs', $entityType, 'collection', 'orderBy']);
|
||||
|
||||
if (is_null($order)) {
|
||||
$order = $metadata->get(['entityDefs', $entityType, 'collection', 'order']) ?? 'asc';
|
||||
}
|
||||
} else {
|
||||
$order = $order ?? 'asc';
|
||||
}
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
$this->throwError("Only core entities are supported.");
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$relationType = $entity->getRelationParam($link, 'type');
|
||||
$relationType = $entity->getRelationType($link);
|
||||
|
||||
if (
|
||||
in_array($relationType, [
|
||||
@@ -148,42 +136,45 @@ class FindRelatedManyType extends BaseFunction implements
|
||||
RelationType::BELONGS_TO_PARENT,
|
||||
])
|
||||
) {
|
||||
$this->throwError("Not supported link type '$relationType'.");
|
||||
throw new NotAllowedUsage("Not supported link type '$relationType'.");
|
||||
}
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, RelationParam::ENTITY);
|
||||
|
||||
if (!$foreignEntityType) {
|
||||
$this->throwError("Bad or not supported link '$link'.");
|
||||
throw new NotAllowedUsage("Bad or not supported link '$link'.");
|
||||
}
|
||||
|
||||
$foreignLink = $entity->getRelationParam($link, RelationParam::FOREIGN);
|
||||
|
||||
if (!$foreignLink) {
|
||||
$this->throwError("Not supported link '$link'.");
|
||||
throw new NotAllowedUsage("Not supported link '$link'.");
|
||||
}
|
||||
|
||||
$builder = $this->injectableFactory->create(SelectBuilderFactory::class)
|
||||
$builder = $this->selectBuilderFactory
|
||||
->create()
|
||||
->forUser($this->user)
|
||||
->withPrimaryFilter(All::NAME)
|
||||
->from($foreignEntityType);
|
||||
|
||||
$this->findQueryUtil->applyOrder($builder, $orderBy, $order, 6);
|
||||
|
||||
$whereClause = [];
|
||||
|
||||
if (count($args) <= 7) {
|
||||
if (count($arguments) <= 7) {
|
||||
$filter = null;
|
||||
if (count($args) == 7) {
|
||||
$filter = $args[6];
|
||||
if (count($arguments) == 7) {
|
||||
$filter = $arguments[6];
|
||||
}
|
||||
|
||||
(new FindQueryUtil())->applyFilter($builder, $filter, 7);
|
||||
$this->findQueryUtil->applyFilter($builder, $filter, 7);
|
||||
} else {
|
||||
$i = 6;
|
||||
|
||||
while ($i < count($args) - 1) {
|
||||
$key = $args[$i];
|
||||
$value = $args[$i + 1];
|
||||
while ($i < count($arguments) - 1) {
|
||||
$key = $arguments[$i];
|
||||
$value = $arguments[$i + 1];
|
||||
|
||||
$this->findQueryUtil->assertWhereClauseKeyValid($entityType, $key);
|
||||
|
||||
$whereClause[] = [$key => $value];
|
||||
|
||||
@@ -194,7 +185,7 @@ class FindRelatedManyType extends BaseFunction implements
|
||||
try {
|
||||
$queryBuilder = $builder->buildQueryBuilder();
|
||||
} catch (BadRequest|Forbidden $e) {
|
||||
throw new Error($e->getMessage(), 0, $e);
|
||||
throw new NotAllowedUsage($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
if (!empty($whereClause)) {
|
||||
@@ -218,10 +209,6 @@ class FindRelatedManyType extends BaseFunction implements
|
||||
|
||||
$queryBuilder->limit(0, $limit);
|
||||
|
||||
if ($orderBy) {
|
||||
$queryBuilder->order($orderBy, $order);
|
||||
}
|
||||
|
||||
$collection = $entityManager
|
||||
->getRDBRepository($foreignEntityType)
|
||||
->clone($queryBuilder->build())
|
||||
@@ -239,8 +226,16 @@ class FindRelatedManyType extends BaseFunction implements
|
||||
|
||||
private function generateRandomString(): string
|
||||
{
|
||||
$generator = $this->injectableFactory->create(RandomStringGenerator::class);
|
||||
return $this->randomStringGenerator->generate();
|
||||
}
|
||||
|
||||
return $generator->generate();
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
private function assertLinkRead(string $entityType, string $link): void
|
||||
{
|
||||
if (!$this->systemRestriction->checkLinkRead($entityType, $link) ) {
|
||||
throw new NotAllowedUsage("Cannot read restricted link $entityType.$link.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,83 +29,92 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
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\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Core\Select\Primary\Filters\All;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class FindRelatedOneType extends BaseFunction implements
|
||||
Di\EntityManagerAware,
|
||||
Di\MetadataAware,
|
||||
Di\InjectableFactoryAware,
|
||||
Di\UserAware
|
||||
class FindRelatedOneType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\MetadataSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
use Di\UserSetter;
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private FindQueryUtil $findQueryUtil,
|
||||
private SystemRestriction $systemRestriction,
|
||||
) {}
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
public function process(EvaluatedArgumentList $arguments): ?string
|
||||
{
|
||||
if (count($args) < 3) {
|
||||
$this->throwTooFewArguments(3);
|
||||
if (count($arguments) < 3) {
|
||||
throw TooFewArguments::create(3);
|
||||
}
|
||||
|
||||
$entityManager = $this->entityManager;
|
||||
|
||||
$entityType = $this->evaluate($args[0]);
|
||||
$id = $this->evaluate($args[1]);
|
||||
$link = $this->evaluate($args[2]);
|
||||
$entityType = $arguments[0];
|
||||
$id = $arguments[1];
|
||||
$link = $arguments[2];
|
||||
|
||||
$orderBy = null;
|
||||
$order = null;
|
||||
|
||||
if (count($args) > 3) {
|
||||
$orderBy = $this->evaluate($args[3]);
|
||||
if (count($arguments) > 3) {
|
||||
$orderBy = $arguments[3];
|
||||
}
|
||||
|
||||
if (count($args) > 4) {
|
||||
$order = $this->evaluate($args[4]) ?? null;
|
||||
if (count($arguments) > 4) {
|
||||
$order = $arguments[4];
|
||||
}
|
||||
|
||||
if (!$entityType) {
|
||||
$this->throwBadArgumentType(1, 'string');
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
return null;
|
||||
if (!is_string($id)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
if (!$link) {
|
||||
$this->throwBadArgumentType(3, 'string');
|
||||
if (!is_string($link)) {
|
||||
throw BadArgumentType::create(3, 'string');
|
||||
}
|
||||
|
||||
if ($orderBy !== null && !is_string($orderBy)) {
|
||||
throw BadArgumentType::create(4, 'string|null');
|
||||
}
|
||||
|
||||
if ($order !== null && !is_string($order) && !is_bool($order)) {
|
||||
throw BadArgumentType::create(5, 'string|bool|null');
|
||||
}
|
||||
|
||||
$this->assertLinkRead($entityType, $link);
|
||||
|
||||
$entity = $entityManager->getEntityById($entityType, $id);
|
||||
|
||||
if (!$entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$metadata = $this->metadata;
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
$this->throwError("Only core entities are supported.");
|
||||
throw new Error("Non-core entity.");
|
||||
}
|
||||
|
||||
$relationType = $entity->getRelationParam($link, 'type');
|
||||
$relationType = $entity->getRelationType($link);
|
||||
|
||||
if (
|
||||
in_array($relationType, [
|
||||
@@ -120,57 +129,46 @@ class FindRelatedOneType extends BaseFunction implements
|
||||
->select([Attribute::ID])
|
||||
->findOne();
|
||||
|
||||
if (!$relatedEntity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $relatedEntity->getId();
|
||||
}
|
||||
|
||||
if (!$orderBy) {
|
||||
$orderBy = $metadata->get(['entityDefs', $entityType, 'collection', 'orderBy']);
|
||||
|
||||
if (is_null($order)) {
|
||||
$order = $metadata->get(['entityDefs', $entityType, 'collection', 'order']) ?? 'ASC';
|
||||
}
|
||||
} else {
|
||||
$order = $order ?? Order::ASC;
|
||||
return $relatedEntity?->getId();
|
||||
}
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, RelationParam::ENTITY);
|
||||
|
||||
if (!$foreignEntityType) {
|
||||
$this->throwError("Bad or not supported link '$link'.");
|
||||
throw new NotAllowedUsage("Bad or not supported link '$link'.");
|
||||
}
|
||||
|
||||
$foreignLink = $entity->getRelationParam($link, RelationParam::FOREIGN);
|
||||
|
||||
if (!$foreignLink) {
|
||||
$this->throwError("Not supported link '$link'.");
|
||||
throw new NotAllowedUsage("Not supported link '$link'.");
|
||||
}
|
||||
|
||||
$builder = $this->injectableFactory->create(SelectBuilderFactory::class)
|
||||
$builder = $this->selectBuilderFactory
|
||||
->create()
|
||||
->forUser($this->user)
|
||||
->withPrimaryFilter(All::NAME)
|
||||
->from($foreignEntityType);
|
||||
|
||||
$this->findQueryUtil->applyOrder($builder, $orderBy, $order, 5);
|
||||
|
||||
$whereClause = [];
|
||||
|
||||
if (count($args) <= 6) {
|
||||
if (count($arguments) <= 6) {
|
||||
$filter = null;
|
||||
|
||||
if (count($args) === 6) {
|
||||
$filter = $this->evaluate($args[5]);
|
||||
if (count($arguments) === 6) {
|
||||
$filter = $arguments[5];
|
||||
}
|
||||
|
||||
(new FindQueryUtil())->applyFilter($builder, $filter, 6);
|
||||
$this->findQueryUtil->applyFilter($builder, $filter, 6);
|
||||
} else {
|
||||
$i = 5;
|
||||
|
||||
while ($i < count($args) - 1) {
|
||||
$key = $this->evaluate($args[$i]);
|
||||
$value = $this->evaluate($args[$i + 1]);
|
||||
while ($i < count($arguments) - 1) {
|
||||
$key = $arguments[$i];
|
||||
$value = $arguments[$i + 1];
|
||||
|
||||
$this->findQueryUtil->assertWhereClauseKeyValid($entityType, $key);
|
||||
|
||||
$whereClause[] = [$key => $value];
|
||||
|
||||
@@ -181,7 +179,7 @@ class FindRelatedOneType extends BaseFunction implements
|
||||
try {
|
||||
$queryBuilder = $builder->buildQueryBuilder();
|
||||
} catch (BadRequest|Forbidden $e) {
|
||||
throw new Error($e->getMessage(), 0, $e);
|
||||
throw new NotAllowedUsage($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
if (!empty($whereClause)) {
|
||||
@@ -197,24 +195,26 @@ class FindRelatedOneType extends BaseFunction implements
|
||||
$queryBuilder
|
||||
->join($foreignLink)
|
||||
->where([
|
||||
$foreignLink . '.id' => $entity->getId(),
|
||||
$foreignLink . '.' . Attribute::ID => $entity->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($orderBy) {
|
||||
$queryBuilder->order($orderBy, $order);
|
||||
}
|
||||
|
||||
$relatedEntity = $entityManager
|
||||
->getRDBRepository($foreignEntityType)
|
||||
->clone($queryBuilder->build())
|
||||
->select([Attribute::ID])
|
||||
->findOne();
|
||||
|
||||
if ($relatedEntity) {
|
||||
return $relatedEntity->getId();
|
||||
}
|
||||
return $relatedEntity?->getId();
|
||||
}
|
||||
|
||||
return null;
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
private function assertLinkRead(string $entityType, string $link): void
|
||||
{
|
||||
if (!$this->systemRestriction->checkLinkRead($entityType, $link) ) {
|
||||
throw new NotAllowedUsage("Cannot read restricted link $entityType.$link.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,23 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\ArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
use Espo\Core\Di;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class RelateType extends BaseFunction implements
|
||||
Di\EntityManagerAware
|
||||
Di\EntityManagerAware,
|
||||
Di\InjectableFactoryAware
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
{
|
||||
@@ -52,21 +60,23 @@ class RelateType extends BaseFunction implements
|
||||
$columnData = count($args) > 4 ? $this->evaluate($args[4]) : null;
|
||||
|
||||
if (!$entityType || !is_string($entityType)) {
|
||||
$this->throwBadArgumentType(1, 'string');
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
return null;
|
||||
if (!is_string($id)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
if (!$link || !is_string($link)) {
|
||||
$this->throwBadArgumentType(3, 'string');
|
||||
throw BadArgumentType::create(3, 'string');
|
||||
}
|
||||
|
||||
if ($columnData !== null && !$columnData instanceof stdClass) {
|
||||
$this->throwBadArgumentType(4, 'object');
|
||||
throw BadArgumentType::create(4, 'object');
|
||||
}
|
||||
|
||||
$this->assertLinkWrite($entityType, $link);
|
||||
|
||||
if ($columnData instanceof stdClass) {
|
||||
$columnData = get_object_vars($columnData);
|
||||
}
|
||||
@@ -105,4 +115,16 @@ class RelateType extends BaseFunction implements
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
private function assertLinkWrite(string $entityType, string $link): void
|
||||
{
|
||||
$restriction = $this->injectableFactory->create(SystemRestriction::class);
|
||||
|
||||
if (!$restriction->checkLinkWrite($entityType, $link) ) {
|
||||
throw new NotAllowedUsage("Cannot write restricted link $entityType.$link.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,17 +29,23 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Formula\{
|
||||
Functions\BaseFunction,
|
||||
ArgumentList,
|
||||
};
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\ArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class RelationColumnType extends BaseFunction implements
|
||||
Di\EntityManagerAware
|
||||
Di\EntityManagerAware,
|
||||
Di\InjectableFactoryAware
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
{
|
||||
@@ -55,26 +61,28 @@ class RelationColumnType extends BaseFunction implements
|
||||
$foreignId = $args[3];
|
||||
$column = $args[4];
|
||||
|
||||
if (!$entityType) {
|
||||
$this->throwError("Empty entityType.");
|
||||
if (!is_string($entityType)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
return null;
|
||||
if (!is_string($id)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
if (!$link) {
|
||||
$this->throwError("Empty link.");
|
||||
if (!is_string($link)) {
|
||||
throw BadArgumentType::create(3, 'string');
|
||||
}
|
||||
|
||||
if (!$foreignId) {
|
||||
return null;
|
||||
if (!is_string($foreignId)) {
|
||||
throw BadArgumentType::create(4, 'string');
|
||||
}
|
||||
|
||||
if (!$column) {
|
||||
$this->throwError("Empty column.");
|
||||
if (!is_string($column)) {
|
||||
throw BadArgumentType::create(5, 'string');
|
||||
}
|
||||
|
||||
$this->assertLinkRead($entityType, $link);
|
||||
|
||||
$em = $this->entityManager;
|
||||
|
||||
if (!$em->hasRepository($entityType)) {
|
||||
@@ -87,6 +95,19 @@ class RelationColumnType extends BaseFunction implements
|
||||
return null;
|
||||
}
|
||||
|
||||
return $em->getRelation($entity, $link)->getColumnById($foreignId, $column);
|
||||
return $em->getRelation($entity, $link)
|
||||
->getColumnById($foreignId, $column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
private function assertLinkRead(string $entityType, string $link): void
|
||||
{
|
||||
$restriction = $this->injectableFactory->create(SystemRestriction::class);
|
||||
|
||||
if (!$restriction->checkLinkRead($entityType, $link) ) {
|
||||
throw new NotAllowedUsage("Cannot read restricted link $entityType.$link.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,20 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\ArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
class UnrelateType extends BaseFunction implements
|
||||
Di\EntityManagerAware
|
||||
Di\EntityManagerAware,
|
||||
Di\InjectableFactoryAware
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
{
|
||||
@@ -50,22 +55,24 @@ class UnrelateType extends BaseFunction implements
|
||||
$link = $this->evaluate($args[2]);
|
||||
$foreignId = $this->evaluate($args[3]);
|
||||
|
||||
if (!$entityType) {
|
||||
$this->throwError("Empty entityType.");
|
||||
if (!is_string($entityType)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
return null;
|
||||
if (!is_string($id)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
if (!$link) {
|
||||
$this->throwError("Empty link.");
|
||||
if (!is_string($link)) {
|
||||
throw BadArgumentType::create(3, 'string');
|
||||
}
|
||||
|
||||
if (!$foreignId) {
|
||||
return null;
|
||||
if (!is_string($foreignId)) {
|
||||
throw BadArgumentType::create(4, 'string');
|
||||
}
|
||||
|
||||
$this->assertLinkWrite($entityType, $link);
|
||||
|
||||
$em = $this->entityManager;
|
||||
|
||||
if (!$em->hasRepository($entityType)) {
|
||||
@@ -84,4 +91,16 @@ class UnrelateType extends BaseFunction implements
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
private function assertLinkWrite(string $entityType, string $link): void
|
||||
{
|
||||
$restriction = $this->injectableFactory->create(SystemRestriction::class);
|
||||
|
||||
if (!$restriction->checkLinkWrite($entityType, $link) ) {
|
||||
throw new NotAllowedUsage("Cannot write restricted link $entityType.$link.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,17 +29,20 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Formula\{
|
||||
Functions\BaseFunction,
|
||||
ArgumentList,
|
||||
};
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\ArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
class UpdateRelationColumnType extends BaseFunction implements
|
||||
Di\EntityManagerAware
|
||||
Di\EntityManagerAware,
|
||||
Di\InjectableFactoryAware
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
{
|
||||
@@ -56,28 +59,30 @@ class UpdateRelationColumnType extends BaseFunction implements
|
||||
$column = $args[4];
|
||||
$value = $args[5];
|
||||
|
||||
if (!$entityType) {
|
||||
$this->throwError("Empty entityType.");
|
||||
if (!is_string($entityType)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
return null;
|
||||
if (!is_string($id)) {
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
if (!$link) {
|
||||
$this->throwError("Empty link.");
|
||||
if (!is_string($link)) {
|
||||
throw BadArgumentType::create(3, 'string');
|
||||
}
|
||||
|
||||
if (!$foreignId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$column) {
|
||||
$this->throwError("Empty column.");
|
||||
if (!is_string($foreignId)) {
|
||||
throw BadArgumentType::create(4, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($column)) {
|
||||
$this->throwError("Column is not string.");
|
||||
throw BadArgumentType::create(5, 'string');
|
||||
}
|
||||
|
||||
$this->assertLinkWrite($entityType, $link);
|
||||
|
||||
if (!$column) {
|
||||
$this->throwError("Empty column.");
|
||||
}
|
||||
|
||||
$em = $this->entityManager;
|
||||
@@ -98,4 +103,16 @@ class UpdateRelationColumnType extends BaseFunction implements
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
private function assertLinkWrite(string $entityType, string $link): void
|
||||
{
|
||||
$restriction = $this->injectableFactory->create(SystemRestriction::class);
|
||||
|
||||
if (!$restriction->checkLinkWrite($entityType, $link) ) {
|
||||
throw new NotAllowedUsage("Cannot write restricted link $entityType.$link.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,46 +29,52 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Formula\ArgumentList;
|
||||
use Espo\Core\Formula\EvaluatedArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
use Espo\Core\Di;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Func;
|
||||
use Espo\Core\Formula\Utils\EntityUtil;
|
||||
use RuntimeException;
|
||||
use Espo\ORM\EntityManager;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class UpdateType extends BaseFunction implements
|
||||
Di\EntityManagerAware
|
||||
class UpdateType implements Func
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private EntityUtil $entityUtil,
|
||||
) {}
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
public function process(EvaluatedArgumentList $arguments): bool
|
||||
{
|
||||
if (count($args) < 2) {
|
||||
$this->throwTooFewArguments(2);
|
||||
if (count($arguments) < 2) {
|
||||
throw TooFewArguments::create(2);
|
||||
}
|
||||
|
||||
$args = $this->evaluate($args);
|
||||
|
||||
if (!is_array($args)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$entityType = $args[0];
|
||||
$id = $args[1];
|
||||
$entityType = $arguments[0];
|
||||
$id = $arguments[1];
|
||||
|
||||
if (!is_string($entityType)) {
|
||||
$this->throwBadArgumentType(1, 'string');
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if (!is_string($id)) {
|
||||
$this->throwBadArgumentType(2, 'string');
|
||||
throw BadArgumentType::create(2, 'string');
|
||||
}
|
||||
|
||||
$data = $this->getData($args, $entityType);
|
||||
$data = $this->getData($arguments, $entityType);
|
||||
|
||||
$notAllowedAttributes = array_intersect(
|
||||
array_keys($data),
|
||||
$this->entityUtil->getWriteRestrictedAttributeList($entityType),
|
||||
);
|
||||
|
||||
if ($notAllowedAttributes) {
|
||||
throw new NotAllowedUsage("Cannot write $entityType.$notAllowedAttributes[0].");
|
||||
}
|
||||
|
||||
$entity = $this->entityManager->getEntityById($entityType, $id);
|
||||
|
||||
@@ -76,9 +82,9 @@ class UpdateType extends BaseFunction implements
|
||||
return false;
|
||||
}
|
||||
|
||||
$entity->set($data);
|
||||
$entity->setMultiple($data);
|
||||
|
||||
EntityUtil::checkUpdateAccess($entity);
|
||||
$this->entityUtil->assertUpdateAccess($entity);
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
|
||||
@@ -86,11 +92,10 @@ class UpdateType extends BaseFunction implements
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $args
|
||||
* @return array<string, mixed>
|
||||
* @throws BadArgumentType
|
||||
*/
|
||||
private function getData(array $args, mixed $entityType): array
|
||||
private function getData(EvaluatedArgumentList $args, mixed $entityType): array
|
||||
{
|
||||
if (count($args) >= 3 && $args[2] instanceof stdClass) {
|
||||
return get_object_vars($args[2]);
|
||||
@@ -104,7 +109,7 @@ class UpdateType extends BaseFunction implements
|
||||
$attribute = $args[$i];
|
||||
|
||||
if (!is_string($entityType)) {
|
||||
$this->throwBadArgumentType($i + 1, 'string');
|
||||
throw BadArgumentType::create($i + 1, 'string');
|
||||
}
|
||||
|
||||
/** @var string $attribute */
|
||||
|
||||
@@ -29,17 +29,25 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup\Util;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentValue;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Core\Select\SelectBuilder;
|
||||
use Espo\Core\Select\Where\Item;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\ORM\QueryComposer\Util;
|
||||
use InvalidArgumentException;
|
||||
use stdClass;
|
||||
|
||||
class FindQueryUtil
|
||||
{
|
||||
public function __construct() {}
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
@@ -71,4 +79,68 @@ class FindQueryUtil
|
||||
throw BadArgumentType::create($position, 'string|object');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadArgumentValue
|
||||
*/
|
||||
public function applyOrder(
|
||||
SelectBuilder $builder,
|
||||
?string $orderBy,
|
||||
string|bool|null $order,
|
||||
int $orderPosition,
|
||||
): void {
|
||||
|
||||
if (is_bool($order)) {
|
||||
$order = $order ? Order::DESC : Order::ASC;
|
||||
}
|
||||
|
||||
if (is_string($order)) {
|
||||
$order = strtoupper($order);
|
||||
|
||||
if ($order !== Order::ASC && $order !== Order::DESC) {
|
||||
throw BadArgumentValue::create($orderPosition, "Order must be 'ASC'|'DESC'|bool|null.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($orderBy) {
|
||||
$builder->withSearchParams(
|
||||
SearchParams::create()
|
||||
->withOrderBy($orderBy)
|
||||
->withOrder($order)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($order !== null) {
|
||||
$builder->withSearchParams(
|
||||
SearchParams::create()
|
||||
->withOrder($order)
|
||||
);
|
||||
}
|
||||
|
||||
$builder->withDefaultOrder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
public function assertWhereClauseKeyValid(string $entityType, string $key): void
|
||||
{
|
||||
try {
|
||||
[$expression] = Util::splitWhereKeyThrowing($key);
|
||||
} catch (InvalidArgumentException) {
|
||||
throw new NotAllowedUsage("Not allowed where key expression '$key'");
|
||||
}
|
||||
|
||||
if (Util::isComplexExpression($expression)) {
|
||||
throw new NotAllowedUsage("Not allowed expression is where key '$key'");
|
||||
}
|
||||
|
||||
$attribute = $expression;
|
||||
|
||||
if (!$this->systemRestriction->checkAttributeRead($entityType, $attribute)) {
|
||||
throw new NotAllowedUsage("Cannot use restricted attribute in where key '$key'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,33 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions;
|
||||
|
||||
use Espo\Core\Formula\Exceptions\BadArgumentType;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\TooFewArguments;
|
||||
use Espo\Core\Formula\Processor;
|
||||
use Espo\Core\Formula\Utils\EntityUtil;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use stdClass;
|
||||
|
||||
class SetAttributeType extends Base
|
||||
{
|
||||
public function __construct(
|
||||
private EntityUtil $entityUtil,
|
||||
string $name,
|
||||
Processor $processor,
|
||||
?Entity $entity = null,
|
||||
?stdClass $variables = null,
|
||||
) {
|
||||
parent::__construct(
|
||||
name: $name,
|
||||
processor: $processor,
|
||||
entity: $entity,
|
||||
variables: $variables,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws Error
|
||||
@@ -43,26 +63,32 @@ class SetAttributeType extends Base
|
||||
public function process(stdClass $item)
|
||||
{
|
||||
if (count($item->value) < 2) {
|
||||
throw new Error("SetAttribute: Too few arguments.");
|
||||
throw TooFewArguments::create(2);
|
||||
}
|
||||
|
||||
$name = $this->evaluate($item->value[0]);
|
||||
$attribute = $this->evaluate($item->value[0]);
|
||||
|
||||
if (!is_string($name)) {
|
||||
throw new Error("SetAttribute: First argument is not string.");
|
||||
if (!is_string($attribute)) {
|
||||
throw BadArgumentType::create(1, 'string');
|
||||
}
|
||||
|
||||
if ($name === Attribute::ID) {
|
||||
throw new Error("Formula set-attribute: Not allowed to set `id` attribute.");
|
||||
if ($attribute === Attribute::ID) {
|
||||
throw new NotAllowedUsage("Not allowed to set `id` attribute.");
|
||||
}
|
||||
|
||||
$value = $this->evaluate($item->value[1]);
|
||||
|
||||
$entity = $this->getEntity();
|
||||
|
||||
$entity->set($name, $value);
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
EntityUtil::checkUpdateAccess($entity);
|
||||
if (in_array($attribute, $this->entityUtil->getWriteRestrictedAttributeList($entityType))) {
|
||||
throw new NotAllowedUsage("Cannot write $entityType.$attribute.");
|
||||
}
|
||||
|
||||
$entity->set($attribute, $value);
|
||||
|
||||
$this->entityUtil->assertUpdateAccess($entity);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -29,8 +29,9 @@
|
||||
|
||||
namespace Espo\Core\Formula\Utils;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Acl\Exceptions\Restricted;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
@@ -39,46 +40,39 @@ use Espo\ORM\Entity;
|
||||
*/
|
||||
class EntityUtil
|
||||
{
|
||||
public function __construct(
|
||||
private SystemRestriction $systemRestriction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
public static function checkUpdateAccess(Entity $entity): void
|
||||
public function assertUpdateAccess(Entity $entity): void
|
||||
{
|
||||
if ($entity instanceof User) {
|
||||
$restrictedTypeList = self::getUserRestrictedTypeList();
|
||||
|
||||
if (
|
||||
$entity->isAttributeChanged(User::ATTR_TYPE) &&
|
||||
(
|
||||
in_array($entity->getFetched(User::ATTR_TYPE), $restrictedTypeList) ||
|
||||
in_array($entity->getType(), $restrictedTypeList)
|
||||
)
|
||||
) {
|
||||
throw new NotAllowedUsage("Cannot change user type.");
|
||||
}
|
||||
try {
|
||||
$this->systemRestriction->assertUpdate($entity);
|
||||
} catch (Restricted $e) {
|
||||
throw new NotAllowedUsage($e->getMessage(), previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotAllowedUsage
|
||||
*/
|
||||
public static function checkRemoveAccess(Entity $entity): void
|
||||
public function assertRemoveAccess(Entity $entity): void
|
||||
{
|
||||
if ($entity instanceof User) {
|
||||
if (in_array($entity->getType(), self::getUserRestrictedTypeList())) {
|
||||
throw new NotAllowedUsage("Cannot remove the user.");
|
||||
}
|
||||
try {
|
||||
$this->systemRestriction->assertRemoval($entity);
|
||||
} catch (Restricted $e) {
|
||||
throw new NotAllowedUsage($e->getMessage(), previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getUserRestrictedTypeList(): array
|
||||
public function getWriteRestrictedAttributeList(string $entityType): array
|
||||
{
|
||||
return [
|
||||
User::TYPE_SUPER_ADMIN,
|
||||
User::TYPE_SYSTEM,
|
||||
];
|
||||
return $this->systemRestriction->getWriteRestrictedAttributeList($entityType);
|
||||
}
|
||||
}
|
||||
|
||||
65
application/Espo/Core/Select/Helpers/EntityHelper.php
Normal file
65
application/Espo/Core/Select/Helpers/EntityHelper.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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\Select\Helpers;
|
||||
|
||||
use Espo\ORM\BaseEntity;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class EntityHelper
|
||||
{
|
||||
public function __construct(
|
||||
private Defs $defs,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getRelationEntityType(Entity $entity, string $relation): ?string
|
||||
{
|
||||
if ($entity instanceof BaseEntity) {
|
||||
return $entity->getRelationParam($relation, RelationParam::ENTITY);
|
||||
}
|
||||
|
||||
$entityDefs = $this->defs->getEntity($entity->getEntityType());
|
||||
|
||||
if (!$entityDefs->hasRelation($relation)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entityDefs
|
||||
->getRelation($relation)
|
||||
->tryGetForeignEntityType();
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ class Applier
|
||||
private OrdererFactory $ordererFactory,
|
||||
private AclManager $aclManager,
|
||||
private User $user,
|
||||
private Acl\SystemRestriction $systemRestriction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -91,6 +92,13 @@ class Applier
|
||||
) {
|
||||
throw new Forbidden("Not access to order by field '$orderBy'.");
|
||||
}
|
||||
|
||||
if (
|
||||
!$params->applyPermissionCheck() &&
|
||||
!$this->systemRestriction->checkFieldRead($this->entityType, $orderBy)
|
||||
) {
|
||||
throw new Forbidden("Cannot order by restricted field '$orderBy'.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($orderBy === null) {
|
||||
|
||||
@@ -32,14 +32,13 @@ namespace Espo\Core\Select\Where;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Select\Helpers\EntityHelper;
|
||||
use Espo\Core\Select\Where\Item\Type;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\QueryComposer\Util;
|
||||
use Espo\ORM\QueryComposer\Util as QueryUtil;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\BaseEntity;
|
||||
|
||||
/**
|
||||
* Checks Where parameters. Throws an exception if anything not allowed is met.
|
||||
@@ -93,6 +92,8 @@ class Checker
|
||||
private string $entityType,
|
||||
private EntityManager $entityManager,
|
||||
private Acl $acl,
|
||||
private Acl\SystemRestriction $systemRestriction,
|
||||
private EntityHelper $entityHelper,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -137,9 +138,7 @@ class Checker
|
||||
foreach ($argumentList as $argument) {
|
||||
$this->checkAttributeExistence($argument, $type);
|
||||
|
||||
if ($checkWherePermission) {
|
||||
$this->checkAttributePermission($argument, $type, $value);
|
||||
}
|
||||
$this->checkAttributePermission($argument, $type, $value, $checkWherePermission);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,40 +176,37 @@ class Checker
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function checkAttributePermission(string $attribute, string $type, mixed $value): void
|
||||
private function checkAttributePermission(string $attribute, string $type, mixed $value, bool $aclCheck): void
|
||||
{
|
||||
$entityType = $this->entityType;
|
||||
|
||||
if (str_contains($attribute, '.')) {
|
||||
[$link, $attribute] = explode('.', $attribute);
|
||||
|
||||
if (!$this->getSeed()->hasRelation($link)) {
|
||||
// TODO allow alias
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
}
|
||||
|
||||
$foreignEntityType = $this->getRelationEntityType($this->getSeed(), $link);
|
||||
|
||||
if (!$foreignEntityType) {
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->acl->checkScope($foreignEntityType) ||
|
||||
in_array($link, $this->acl->getScopeForbiddenLinkList($entityType))
|
||||
) {
|
||||
throw new Forbidden("Forbidden relation '$link' in where.");
|
||||
}
|
||||
|
||||
if (in_array($attribute, $this->acl->getScopeForbiddenAttributeList($foreignEntityType))) {
|
||||
throw new Forbidden("Forbidden attribute '$link.$attribute' in where.");
|
||||
}
|
||||
$this->checkAttributePermissionWithLink(
|
||||
entityType: $entityType,
|
||||
aclCheck: $aclCheck,
|
||||
link: $link,
|
||||
attribute: $attribute,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($type, $this->linkTypeList)) {
|
||||
$this->checkLink($type, $entityType, $attribute, $value);
|
||||
$this->checkLink(
|
||||
type: $type,
|
||||
entityType: $entityType,
|
||||
link: $attribute,
|
||||
value: $value,
|
||||
aclCheck: $aclCheck,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$aclCheck) {
|
||||
$this->assertAttributeSystemRead($entityType, $attribute);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -220,6 +216,58 @@ class Checker
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function checkAttributePermissionWithLink(
|
||||
string $entityType,
|
||||
bool $aclCheck,
|
||||
string $link,
|
||||
string $attribute,
|
||||
): void {
|
||||
|
||||
if (!$link) {
|
||||
throw new BadRequest("Empty relation in path in where.");
|
||||
}
|
||||
|
||||
if (!$attribute) {
|
||||
throw new BadRequest("Empty attribute in path in where.");
|
||||
}
|
||||
|
||||
if (!$this->getSeed()->hasRelation($link)) {
|
||||
// TODO allow alias
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
}
|
||||
|
||||
$foreignEntityType = $this->getRelationEntityType($this->getSeed(), $link);
|
||||
|
||||
if (!$foreignEntityType) {
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
}
|
||||
|
||||
if (!$aclCheck) {
|
||||
$this->assertLinkSystemRead($entityType, $link);
|
||||
|
||||
if (!$this->systemRestriction->checkAttributeRead($foreignEntityType, $attribute)) {
|
||||
throw new Forbidden("System restricted attribute '$link.$attribute' in where.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->acl->checkScope($foreignEntityType) ||
|
||||
in_array($link, $this->acl->getScopeForbiddenLinkList($entityType))
|
||||
) {
|
||||
throw new Forbidden("Forbidden relation '$link' in where.");
|
||||
}
|
||||
|
||||
if (in_array($attribute, $this->acl->getScopeForbiddenAttributeList($foreignEntityType))) {
|
||||
throw new Forbidden("Forbidden attribute '$link.$attribute' in where.");
|
||||
}
|
||||
}
|
||||
|
||||
private function getSeed(): Entity
|
||||
{
|
||||
$this->seed ??= $this->entityManager->getNewEntity($this->entityType);
|
||||
@@ -227,29 +275,23 @@ class Checker
|
||||
return $this->seed;
|
||||
}
|
||||
|
||||
private function getRelationEntityType(Entity $entity, string $relation): mixed
|
||||
private function getRelationEntityType(Entity $entity, string $relation): ?string
|
||||
{
|
||||
if ($entity instanceof BaseEntity) {
|
||||
return $entity->getRelationParam($relation, RelationParam::ENTITY);
|
||||
}
|
||||
|
||||
$entityDefs = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($entity->getEntityType());
|
||||
|
||||
if (!$entityDefs->hasRelation($relation)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entityDefs->getRelation($relation)->getParam(RelationParam::ENTITY);
|
||||
return $this->entityHelper->getRelationEntityType($entity, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function checkLink(string $type, string $entityType, string $link, mixed $value): void
|
||||
{
|
||||
private function checkLink(
|
||||
string $type,
|
||||
string $entityType,
|
||||
string $link,
|
||||
mixed $value,
|
||||
bool $aclCheck,
|
||||
): void {
|
||||
|
||||
if (!$this->getSeed()->hasRelation($link)) {
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
}
|
||||
@@ -257,19 +299,24 @@ class Checker
|
||||
$foreignEntityType = $this->getRelationEntityType($this->getSeed(), $link);
|
||||
|
||||
if (!$foreignEntityType) {
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
throw new Forbidden("Bad relation '$link' in where, no foreign entity type.");
|
||||
}
|
||||
|
||||
if ($type === self::TYPE_IS_USER_FROM_TEAMS) {
|
||||
$foreignEntityType = Team::ENTITY_TYPE;
|
||||
}
|
||||
|
||||
if (
|
||||
in_array($link, $this->acl->getScopeForbiddenFieldList($entityType)) ||
|
||||
!$this->acl->checkScope($foreignEntityType) ||
|
||||
in_array($link, $this->acl->getScopeForbiddenLinkList($entityType))
|
||||
) {
|
||||
throw new Forbidden("Forbidden relation '$link' in where.");
|
||||
if ($aclCheck) {
|
||||
if (
|
||||
in_array($link, $this->acl->getScopeForbiddenFieldList($entityType)) ||
|
||||
!$this->acl->checkScope($foreignEntityType) ||
|
||||
in_array($link, $this->acl->getScopeForbiddenLinkList($entityType))
|
||||
) {
|
||||
throw new Forbidden("Forbidden link '$link' in where.");
|
||||
}
|
||||
} else {
|
||||
$this->assertLinkSystemRead($entityType, $link);
|
||||
$this->assertFieldSystemRead($entityType, $link);
|
||||
}
|
||||
|
||||
if (!in_array($type, $this->linkWithIdsTypeList)) {
|
||||
@@ -299,9 +346,39 @@ class Checker
|
||||
throw new Forbidden("Record '$foreignEntityType' `$id` not found.");
|
||||
}
|
||||
|
||||
if (!$this->acl->checkEntityRead($entity)) {
|
||||
if ($aclCheck && !$this->acl->checkEntityRead($entity)) {
|
||||
throw new Forbidden("No access to '$foreignEntityType' `$id`.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function assertLinkSystemRead(string $entityType, string $link): void
|
||||
{
|
||||
if (!$this->systemRestriction->checkLinkRead($entityType, $link)) {
|
||||
throw new Forbidden("System restricted link '$link' in where.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function assertFieldSystemRead(string $entityType, string $field): void
|
||||
{
|
||||
if (!$this->systemRestriction->checkFieldRead($entityType, $field)) {
|
||||
throw new Forbidden("System restricted field '$field' in where.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function assertAttributeSystemRead(string $entityType, string $attribute): void
|
||||
{
|
||||
if (!$this->systemRestriction->checkAttributeRead($entityType, $attribute)) {
|
||||
throw new Forbidden("System restricted attribute '$attribute' in where.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,32 +108,6 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
|
||||
protected const EXISTS_OPERATOR = 'EXISTS';
|
||||
|
||||
/** @var string[] */
|
||||
private array $comparisonOperators = [
|
||||
'!=s',
|
||||
'=s',
|
||||
'!=',
|
||||
'!*',
|
||||
'*',
|
||||
'>=',
|
||||
'<=',
|
||||
'>',
|
||||
'<',
|
||||
'=',
|
||||
'>=any',
|
||||
'<=any',
|
||||
'>any',
|
||||
'<any',
|
||||
'!=any',
|
||||
'=any',
|
||||
'>=all',
|
||||
'<=all',
|
||||
'>all',
|
||||
'<all',
|
||||
'!=all',
|
||||
'=all',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected array $comparisonOperatorMap = [
|
||||
'!=s' => 'NOT IN',
|
||||
@@ -2522,23 +2496,11 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
*/
|
||||
private function splitWhereLeftItem(string $item): array
|
||||
{
|
||||
if (preg_match('/^[a-z0-9]+$/i', $item)) {
|
||||
return [$item, '=', '='];
|
||||
}
|
||||
[$expression, $operator] = Util::splitWhereKey($item);
|
||||
|
||||
foreach ($this->comparisonOperators as $operator) {
|
||||
$sqlOperator = $this->comparisonOperatorMap[$operator] ?? $operator;
|
||||
$sqlOperator = $this->comparisonOperatorMap[$operator] ?? $operator;
|
||||
|
||||
if (!str_ends_with($item, $operator)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expression = trim(substr($item, 0, -strlen($operator)));
|
||||
|
||||
return [$expression, $sqlOperator, $operator];
|
||||
}
|
||||
|
||||
return [$item, '=', '='];
|
||||
return [$expression, $sqlOperator, $operator];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,8 +29,74 @@
|
||||
|
||||
namespace Espo\ORM\QueryComposer;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Util
|
||||
{
|
||||
/** @var string[] */
|
||||
private const array COMPARISON_OPERATORS = [
|
||||
'!=s',
|
||||
'=s',
|
||||
'!=',
|
||||
'!*',
|
||||
'*',
|
||||
'>=',
|
||||
'<=',
|
||||
'>',
|
||||
'<',
|
||||
'=',
|
||||
'>=any',
|
||||
'<=any',
|
||||
'>any',
|
||||
'<any',
|
||||
'!=any',
|
||||
'=any',
|
||||
'>=all',
|
||||
'<=all',
|
||||
'>all',
|
||||
'<all',
|
||||
'!=all',
|
||||
'=all',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: string}
|
||||
* @internal
|
||||
* @since 9.4.0
|
||||
*/
|
||||
public static function splitWhereKey(string $whereKey): array
|
||||
{
|
||||
try {
|
||||
return self::splitWhereKeyThrowing($whereKey);
|
||||
} catch (InvalidArgumentException) {}
|
||||
|
||||
return [$whereKey, '='];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: string}
|
||||
* @internal
|
||||
* @since 9.4.0
|
||||
*/
|
||||
public static function splitWhereKeyThrowing(string $whereKey): array
|
||||
{
|
||||
if (preg_match('/^[a-z0-9]+$/i', $whereKey)) {
|
||||
return [$whereKey, '='];
|
||||
}
|
||||
|
||||
foreach (self::COMPARISON_OPERATORS as $operator) {
|
||||
if (!str_ends_with($whereKey, $operator)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expression = trim(substr($whereKey, 0, -strlen($operator)));
|
||||
|
||||
return [$expression, $operator];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
public static function isComplexExpression(string $string): bool
|
||||
{
|
||||
if (
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"systemWriteForbidden": true
|
||||
}
|
||||
@@ -30,5 +30,6 @@
|
||||
"modifiedAt": {
|
||||
"readOnly": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"systemWriteForbidden": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"systemWriteForbidden": true
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"systemWriteForbidden": true
|
||||
}
|
||||
3
application/Espo/Resources/metadata/entityAcl/Job.json
Normal file
3
application/Espo/Resources/metadata/entityAcl/Job.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"systemWriteForbidden": true
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"fields": {
|
||||
"auth2FATotpSecret": {
|
||||
"forbidden": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"systemWriteForbidden": {
|
||||
"type": "boolean",
|
||||
"description": "Restricts record update and removal in formula and other potential configuration tools. As of v9.4."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace tests\integration\Espo\Core\Formula;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Field\DateTimeOptional;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\NotAllowedUsage;
|
||||
use Espo\Core\Formula\Exceptions\UnsafeFunction;
|
||||
use Espo\Core\Formula\Manager;
|
||||
use Espo\Entities\User;
|
||||
@@ -44,7 +45,7 @@ use tests\integration\Core\BaseTestCase;
|
||||
|
||||
class FormulaTest extends BaseTestCase
|
||||
{
|
||||
public function testCountRelatedAndSumRelated()
|
||||
public function testCountRelatedAndSumRelated(): void
|
||||
{
|
||||
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
|
||||
|
||||
@@ -252,7 +253,7 @@ class FormulaTest extends BaseTestCase
|
||||
$this->assertEquals(null, $result);
|
||||
}
|
||||
|
||||
public function testFindMany(): void
|
||||
public function testRecordFindMany(): void
|
||||
{
|
||||
$fm = $this->getContainer()->getByClass(Manager::class);
|
||||
$em = $this->getContainer()->getByClass(EntityManager::class);
|
||||
@@ -330,7 +331,7 @@ class FormulaTest extends BaseTestCase
|
||||
'parentId' => $account->getId(),
|
||||
]);
|
||||
|
||||
$c0 = $em->createEntity('Contact', [
|
||||
$em->createEntity('Contact', [
|
||||
'lastName' => '0',
|
||||
]);
|
||||
|
||||
@@ -1195,4 +1196,370 @@ class FormulaTest extends BaseTestCase
|
||||
$result = $fm->run($script, $account1);
|
||||
$this->assertEquals(100.0, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testRestrictedRead(): void
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
$fm = $this->getContainer()->getByClass(Manager::class);
|
||||
|
||||
$user = $em->createEntity(User::ENTITY_TYPE);
|
||||
$userId = $user->getId();
|
||||
|
||||
$script = "record\\attribute('User', '$userId', 'password')";
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "record\\attribute('User', '$userId', 'userDataId')";
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testRestrictedWrite(): void
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
$fm = $this->getContainer()->getByClass(Manager::class);
|
||||
|
||||
$user = $em->createEntity(User::ENTITY_TYPE);
|
||||
$userId = $user->getId();
|
||||
|
||||
$script = "record\\update('User', '$userId', 'userDataId', '1')";
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testEntityFunctions(): void
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
$fm = $this->getContainer()->getByClass(Manager::class);
|
||||
|
||||
$account = $em->createEntity('Account');
|
||||
/** @var Contact $contact */
|
||||
$contact = $em->createEntity('Contact', ['accountId' => $account->getId()]);
|
||||
$team = $em->createEntity('Team');
|
||||
|
||||
//
|
||||
|
||||
$script = "entity\\addLinkMultipleId('teams', '{$team->getId()}')";
|
||||
$fm->run($script, $account);
|
||||
|
||||
$this->assertEquals([$team->getId()], $account->get('teamsIds'));
|
||||
|
||||
//
|
||||
|
||||
$script = "entity\\hasLinkMultipleId('teams', '{$team->getId()}')";
|
||||
$result = $fm->run($script, $account);
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
//
|
||||
|
||||
$script = "entity\\removeLinkMultipleId('teams', '{$team->getId()}')";
|
||||
$fm->run($script, $account);
|
||||
|
||||
$this->assertEquals([], $account->get('teamsIds'));
|
||||
|
||||
//
|
||||
|
||||
$script = "entity\\clearAttribute('teamsIds')";
|
||||
$fm->run($script, $account);
|
||||
|
||||
$this->assertFalse($account->has('teamsIds'));
|
||||
|
||||
//
|
||||
|
||||
$script = "entity\\setLinkMultipleColumn('accounts', '{$account->getId()}', 'role', 'Tester')";
|
||||
$fm->run($script, $contact);
|
||||
|
||||
$this->assertEquals('Tester', $contact->getLinkMultipleColumn('accounts', 'role', $account->getId()));
|
||||
|
||||
//
|
||||
|
||||
$em->saveEntity($contact);
|
||||
|
||||
$script = "entity\\getLinkColumn('accounts', '{$account->getId()}', 'role')";
|
||||
$value = $fm->run($script, $contact);
|
||||
|
||||
$this->assertEquals('Tester', $value);
|
||||
|
||||
//
|
||||
|
||||
$script = "entity\\countRelated('accounts')";
|
||||
$value = $fm->run($script, $contact);
|
||||
|
||||
$this->assertEquals(1, $value);
|
||||
|
||||
//
|
||||
|
||||
$script = "entity\\isRelated('accounts', '{$account->getId()}')";
|
||||
$value = $fm->run($script, $contact);
|
||||
|
||||
$this->assertTrue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testSystemRestriction(): void
|
||||
{
|
||||
$fm = $this->getContainer()->getByClass(Manager::class);
|
||||
$user = $this->getContainer()->getByClass(User::class);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
record\\findOne('User', 'password', 'ASC');
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
record\\findOne('User', null, null, 'password', '1');
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
record\\findMany('User', 1, null, null, 'password', '1');
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
\$item = object\\create();
|
||||
\$item['attribute'] = 'password';
|
||||
\$item['type'] = 'equals';
|
||||
\$item['value'] = '1';
|
||||
|
||||
record\\findOne('User', null, null, \$item);
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
record\\findRelatedOne('User', '{$user->getId()}', 'userData');
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
record\\findRelatedMany('User', '{$user->getId()}', 'userData', 1);
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
entity\\countRelated('userData');
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script, $user);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
entity\\sumRelated('userData', 'id');
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script, $user);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
\$a = password;
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script, $user);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
authLogRecordId = '1';
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script, $user);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
\$a = entity\\attribute('password');
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script, $user);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
\$a = record\\attribute('User', '{$user->getId()}', 'password');
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
record\\fetch('User', '{$user->getId()}');
|
||||
";
|
||||
|
||||
$data = $fm->run($script);
|
||||
|
||||
$this->assertFalse(property_exists($data, 'password'));
|
||||
$this->assertTrue(property_exists($data, 'name'));
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
record\\update('User', '{$user->getId()}', 'authLogRecordId' , '1');
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script, $user);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
|
||||
$script = "
|
||||
\$data = object\\create();
|
||||
record\\create('Job', \$data);
|
||||
";
|
||||
|
||||
$thrown = false;
|
||||
try {
|
||||
$fm->run($script, $user);
|
||||
} catch (NotAllowedUsage) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,14 @@
|
||||
|
||||
namespace tests\unit\Espo\Core\Formula;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\FieldProcessing\SpecificFieldLoader;
|
||||
use Espo\Core\Formula\Evaluator;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\UndefinedKey;
|
||||
use Espo\Core\Formula\Exceptions\UnsafeFunction;
|
||||
use Espo\Core\Formula\Utils\EntityUtil;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Formula\Exceptions\SyntaxError;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
@@ -64,8 +66,17 @@ class EvaluatorTest extends TestCase
|
||||
'fieldUtil' => $this->createMock(FieldUtil::class),
|
||||
]);
|
||||
|
||||
$restriction = $this->createMock(SystemRestriction::class);
|
||||
$restriction
|
||||
->method('checkAttributeRead')
|
||||
->willReturn(true);
|
||||
|
||||
$entityUtil = $this->createMock(EntityUtil::class);
|
||||
|
||||
$bindingContainer = BindingContainerBuilder::create()
|
||||
->bindInstance(SpecificFieldLoader::class, $this->createMock(SpecificFieldLoader::class))
|
||||
->bindInstance(SystemRestriction::class, $restriction)
|
||||
->bindInstance(EntityUtil::class, $entityUtil)
|
||||
->build();
|
||||
|
||||
$injectableFactory = new InjectableFactory($container, $bindingContainer);
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
namespace tests\unit\Espo\Core\Formula;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\FieldProcessing\SpecificFieldLoader;
|
||||
use Espo\Core\Formula\AttributeFetcher;
|
||||
@@ -41,6 +42,7 @@ use Espo\Core\Formula\Parser\Ast\Value;
|
||||
use Espo\Core\Formula\Parser\Ast\Variable;
|
||||
use Espo\Core\Formula\Processor;
|
||||
use Espo\Core\Formula\Argument;
|
||||
use Espo\Core\Formula\Utils\EntityUtil;
|
||||
use Espo\Core\Utils\DateTime;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
use Espo\Core\Utils\NumberUtil;
|
||||
@@ -48,11 +50,12 @@ use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Crm\Entities\Account;
|
||||
use Espo\ORM\Entity as Entity;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\ORM\Repository\RDBRelation;
|
||||
use Espo\ORM\Repository\RDBRepository;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use stdClass;
|
||||
use tests\unit\ContainerMocker;
|
||||
@@ -61,56 +64,18 @@ class FormulaTest extends TestCase
|
||||
{
|
||||
private $entity;
|
||||
private $entityManager;
|
||||
private $applicationConfig;
|
||||
private $container;
|
||||
private $injectableFactory;
|
||||
private $restriction;
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
$this->entity = $this->getEntityMock();
|
||||
|
||||
$this->entityManager = $this->createMock(EntityManager::class);
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
$dateTime = new DateTime();
|
||||
|
||||
$number = new NumberUtil();
|
||||
|
||||
$config = $this->createMock(Config::class);
|
||||
$config
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
->willReturnMap([
|
||||
['timeZone', null, 'UTC']
|
||||
]);
|
||||
|
||||
$this->applicationConfig = $this->createMock(Config\ApplicationConfig::class);
|
||||
$this->applicationConfig
|
||||
->expects($this->any())
|
||||
->method('getTimeZone')
|
||||
->willReturn('UTC');
|
||||
|
||||
$user = $this->createMock(User::class);
|
||||
$log = $this->createMock(Log::class);
|
||||
|
||||
$user->set('id', '1');
|
||||
|
||||
$user
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
->willReturnMap([
|
||||
['id', '1']
|
||||
]);
|
||||
|
||||
$containerMocker = new ContainerMocker($this);
|
||||
|
||||
$this->container = $containerMocker->create([
|
||||
'entityManager' => $this->entityManager,
|
||||
'dateTime' => $dateTime,
|
||||
'number' => $number,
|
||||
'config' => $config,
|
||||
'user' => $user,
|
||||
'log' => $log,
|
||||
]);
|
||||
$this->ininContainer();
|
||||
}
|
||||
|
||||
private static function stringToNode(string $string): mixed
|
||||
@@ -141,30 +106,28 @@ class FormulaTest extends TestCase
|
||||
|
||||
protected function createProcessor($variables = null, ?Entity $entity = null)
|
||||
{
|
||||
$injectableFactory = new InjectableFactory(
|
||||
$this->container,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(Config\ApplicationConfig::class, $this->applicationConfig)
|
||||
->build()
|
||||
);
|
||||
|
||||
$fieldUtil = $this->createMock(FieldUtil::class);
|
||||
$loader = $this->createMock(SpecificFieldLoader::class);
|
||||
|
||||
$attributeFetcher = new AttributeFetcher($this->entityManager, $fieldUtil, $loader);
|
||||
$attributeFetcher = new AttributeFetcher(
|
||||
entityManager: $this->entityManager,
|
||||
fieldUtil: $fieldUtil,
|
||||
specificFieldLoader: $loader,
|
||||
systemRestriction: $this->restriction,
|
||||
);
|
||||
|
||||
return new Processor(
|
||||
$injectableFactory,
|
||||
$attributeFetcher,
|
||||
null,
|
||||
$entity ?? $this->entity,
|
||||
$variables
|
||||
injectableFactory: $this->injectableFactory,
|
||||
attributeFetcher: $attributeFetcher,
|
||||
functionClassNameMap: null,
|
||||
entity: $entity ?? $this->entity,
|
||||
variables: $variables,
|
||||
);
|
||||
}
|
||||
|
||||
protected function getEntityMock()
|
||||
protected function getEntityMock(): Entity & MockObject
|
||||
{
|
||||
return $this->getMockBuilder(Entity::class)->disableOriginalConstructor()->getMock();
|
||||
return $this->createMock(Entity::class);
|
||||
}
|
||||
|
||||
protected function setEntityAttributes($entity, $attributes)
|
||||
@@ -313,74 +276,6 @@ class FormulaTest extends TestCase
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
function testAddLinkMultipleId()
|
||||
{
|
||||
$item = new Argument(self::stringToNode('
|
||||
{
|
||||
"type": "entity\\\\addLinkMultipleId",
|
||||
"value": [
|
||||
{
|
||||
"type": "value",
|
||||
"value": "teams"
|
||||
},
|
||||
{
|
||||
"type": "value",
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
'));
|
||||
|
||||
$entity = $this->createMock(CoreEntity::class);
|
||||
|
||||
$this->setEntityAttributes($entity, [
|
||||
'teamsIds' => ['2']
|
||||
]);
|
||||
|
||||
$entity
|
||||
->expects($this->any())
|
||||
->method('addLinkMultipleId')
|
||||
->with('teams', '1');
|
||||
|
||||
$this->createProcessor(null, $entity)->process($item);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
function testRemoveLinkMultipleId()
|
||||
{
|
||||
$item = new Argument(self::stringToNode('
|
||||
{
|
||||
"type": "entity\\\\removeLinkMultipleId",
|
||||
"value": [
|
||||
{
|
||||
"type": "value",
|
||||
"value": "teams"
|
||||
},
|
||||
{
|
||||
"type": "value",
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
'));
|
||||
|
||||
$entity = $this->createMock(CoreEntity::class);
|
||||
|
||||
$this->setEntityAttributes($entity, [
|
||||
'teamsIds' => ['1', '2']
|
||||
]);
|
||||
|
||||
$entity
|
||||
->expects($this->any())
|
||||
->method('removeLinkMultipleId')
|
||||
->with('teams', '1');
|
||||
|
||||
$this->createProcessor(null, $entity)->process($item);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
function testAnd()
|
||||
{
|
||||
$item = new Argument(self::stringToNode('
|
||||
@@ -1004,16 +899,9 @@ class FormulaTest extends TestCase
|
||||
'amount' => 3
|
||||
]);
|
||||
|
||||
$repository = $this->createMock(RDBRepository::class);
|
||||
$relation = $this->createMock(RDBRelation::class);
|
||||
|
||||
$this->entityManager
|
||||
->expects($this->once())
|
||||
->method('getRDBRepository')
|
||||
->with($this->entity->getEntityType())
|
||||
->willReturn($repository);
|
||||
|
||||
$repository
|
||||
->expects($this->once())
|
||||
->method('getRelation')
|
||||
->with($this->entity, 'parent')
|
||||
@@ -1363,29 +1251,6 @@ class FormulaTest extends TestCase
|
||||
$this->createProcessor($variables)->process($item);
|
||||
}
|
||||
|
||||
function testClearAttribute(): void
|
||||
{
|
||||
$item = new Argument(self::stringToNode('
|
||||
{
|
||||
"type": "entity\\\\clearAttribute",
|
||||
"value": [
|
||||
{
|
||||
"type": "value",
|
||||
"value": "amount"
|
||||
}
|
||||
]
|
||||
}
|
||||
'));
|
||||
|
||||
$this->entity
|
||||
->expects($this->once())
|
||||
->method('clear')
|
||||
->with('amount');
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$this->createProcessor((object) [])->process($item);
|
||||
}
|
||||
|
||||
function testCompareDates()
|
||||
{
|
||||
$item = new Argument(self::stringToNode('
|
||||
@@ -3173,14 +3038,94 @@ class FormulaTest extends TestCase
|
||||
}
|
||||
'));
|
||||
|
||||
$variables = (object)[];
|
||||
$this->setEntityAttributes($this->entity, array(
|
||||
$variables = (object) [];
|
||||
$this->setEntityAttributes($this->entity, [
|
||||
'test' => 'hello'
|
||||
));
|
||||
]);
|
||||
|
||||
$this->createProcessor($variables)->process($item);
|
||||
|
||||
$this->assertEquals(5, $variables->counter);
|
||||
$this->assertEquals('hello', $variables->test);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \PHPUnit\Framework\MockObject\Exception
|
||||
*/
|
||||
private function ininContainer(): void
|
||||
{
|
||||
$dateTime = new DateTime();
|
||||
|
||||
$number = new NumberUtil();
|
||||
|
||||
$config = $this->createMock(Config::class);
|
||||
$config
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
->willReturnMap([
|
||||
['timeZone', null, 'UTC']
|
||||
]);
|
||||
|
||||
$restriction = $this->createMock(SystemRestriction::class);
|
||||
|
||||
$restriction
|
||||
->method('checkAttributeRead')
|
||||
->willReturn(true);
|
||||
|
||||
$restriction
|
||||
->method('checkLinkRead')
|
||||
->willReturn(true);
|
||||
|
||||
$restriction
|
||||
->method('checkLinkWrite')
|
||||
->willReturn(true);
|
||||
|
||||
$restriction
|
||||
->method('checkFieldWrite')
|
||||
->willReturn(true);
|
||||
|
||||
$this->restriction = $restriction;
|
||||
|
||||
$applicationConfig = $this->createMock(Config\ApplicationConfig::class);
|
||||
$applicationConfig
|
||||
->expects($this->any())
|
||||
->method('getTimeZone')
|
||||
->willReturn('UTC');
|
||||
|
||||
$user = $this->createMock(User::class);
|
||||
$log = $this->createMock(Log::class);
|
||||
|
||||
$user->set('id', '1');
|
||||
|
||||
$user
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
->willReturnMap([
|
||||
['id', '1']
|
||||
]);
|
||||
|
||||
$containerMocker = new ContainerMocker($this);
|
||||
|
||||
$container = $containerMocker->create([
|
||||
'entityManager' => $this->entityManager,
|
||||
'dateTime' => $dateTime,
|
||||
'number' => $number,
|
||||
'config' => $config,
|
||||
'user' => $user,
|
||||
'log' => $log,
|
||||
]);
|
||||
|
||||
$entityUtil = $this->createMock(EntityUtil::class);
|
||||
|
||||
$this->injectableFactory = new InjectableFactory(
|
||||
$container,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(Config\ApplicationConfig::class, $applicationConfig)
|
||||
->bindInstance(SystemRestriction::class, $this->restriction)
|
||||
->bindInstance(EntityUtil::class, $entityUtil)
|
||||
->bindInstance(Entity::class, $this->entity)
|
||||
->build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace tests\unit\Espo\Core\Select\Applier\Appliers;
|
||||
|
||||
use Espo\Core\Acl\SystemRestriction;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Entities\User;
|
||||
@@ -68,15 +69,21 @@ class OrderApplierTest extends TestCase
|
||||
$aclManager = $this->createMock(AclManager::class);
|
||||
$user = $this->createMock(User::class);
|
||||
|
||||
$restriction = $this->createMock(SystemRestriction::class);
|
||||
$restriction
|
||||
->method('checkFieldRead')
|
||||
->willReturn(true);
|
||||
|
||||
$this->entityType = 'Test';
|
||||
|
||||
$this->applier = new OrderApplier(
|
||||
$this->entityType,
|
||||
$this->metadataProvider,
|
||||
$this->itemConverterFactory,
|
||||
$this->ordererFactory,
|
||||
$aclManager,
|
||||
$user,
|
||||
entityType: $this->entityType,
|
||||
metadataProvider: $this->metadataProvider,
|
||||
itemConverterFactory: $this->itemConverterFactory,
|
||||
ordererFactory: $this->ordererFactory,
|
||||
aclManager: $aclManager,
|
||||
user: $user,
|
||||
systemRestriction: $restriction,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Select\Helpers\EntityHelper;
|
||||
use Espo\Core\Select\Where\Checker;
|
||||
use Espo\Core\Select\Where\Item;
|
||||
use Espo\Core\Select\Where\Params;
|
||||
@@ -44,11 +45,12 @@ class CheckerTest extends TestCase
|
||||
{
|
||||
/** @var Checker|null */
|
||||
protected $checker = null;
|
||||
/** @var EntityManager|null */
|
||||
protected $entityManager = null;
|
||||
protected ?EntityManager $entityManager = null;
|
||||
/** @var Acl|null */
|
||||
protected $acl = null;
|
||||
|
||||
protected ?EntityHelper $entityHelper = null;
|
||||
|
||||
protected ?string $entityType = null;
|
||||
protected ?string $foreignEntityType = null;
|
||||
|
||||
@@ -59,14 +61,30 @@ class CheckerTest extends TestCase
|
||||
{
|
||||
$this->entityManager = $this->createMock(EntityManager::class);
|
||||
$this->acl = $this->createMock(Acl::class);
|
||||
$systemRestriction = $this->createMock(Acl\SystemRestriction::class);
|
||||
$this->entityHelper = $this->createMock(EntityHelper::class);
|
||||
|
||||
$systemRestriction
|
||||
->method('checkAttributeRead')
|
||||
->willReturn(true);
|
||||
|
||||
$systemRestriction
|
||||
->method('checkLinkRead')
|
||||
->willReturn(true);
|
||||
|
||||
$systemRestriction
|
||||
->method('checkFieldRead')
|
||||
->willReturn(true);
|
||||
|
||||
$this->entityType = 'Test';
|
||||
$this->foreignEntityType = 'TestForeign';
|
||||
|
||||
$this->checker = new Checker(
|
||||
$this->entityType,
|
||||
$this->entityManager,
|
||||
$this->acl,
|
||||
entityType: $this->entityType,
|
||||
entityManager: $this->entityManager,
|
||||
acl: $this->acl,
|
||||
systemRestriction: $systemRestriction,
|
||||
entityHelper: $this->entityHelper,
|
||||
);
|
||||
|
||||
$this->params = $this->createMock(Params::class);
|
||||
@@ -124,8 +142,21 @@ class CheckerTest extends TestCase
|
||||
['test2', true],
|
||||
]);
|
||||
|
||||
$this->entityHelper
|
||||
->method('getRelationEntityType')
|
||||
->willReturnMap([
|
||||
[$this->entity, 'test3', 'AnotherEntity'],
|
||||
]);
|
||||
|
||||
$another = $this->createMock(Entity::class);
|
||||
|
||||
$this->entityManager
|
||||
->method('getEntityById')
|
||||
->willReturnMap([
|
||||
['AnotherEntity', 'value3', $another]
|
||||
]);
|
||||
|
||||
$this->entity
|
||||
->expects($this->once())
|
||||
->method('hasRelation')
|
||||
->with('test3')
|
||||
->willReturn(true);
|
||||
@@ -297,6 +328,17 @@ class CheckerTest extends TestCase
|
||||
[$foreign, true]
|
||||
]);
|
||||
|
||||
$this->entity
|
||||
->method('hasRelation')
|
||||
->with('test3')
|
||||
->willReturn(true);
|
||||
|
||||
$this->entityHelper
|
||||
->method('getRelationEntityType')
|
||||
->willReturnMap([
|
||||
[$this->entity, 'test3', $this->foreignEntityType],
|
||||
]);
|
||||
|
||||
$this->checker->check($item, $this->params);
|
||||
|
||||
$this->assertTrue(true);
|
||||
|
||||
Reference in New Issue
Block a user