diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/Attachments.php b/application/Espo/Core/Utils/Database/Orm/Relations/Attachments.php index a565f001ca..f60b93ab8e 100644 --- a/application/Espo/Core/Utils/Database/Orm/Relations/Attachments.php +++ b/application/Espo/Core/Utils/Database/Orm/Relations/Attachments.php @@ -35,20 +35,29 @@ class Attachments extends HasChildren { $parentRelation = parent::load($linkName, $entityName); - $relation = array( - $entityName => array ( - 'fields' => array( - $linkName.'Types' => array( + $relation = [ + $entityName => [ + 'fields' => [ + $linkName.'Types' => [ 'type' => 'jsonObject', 'notStorable' => true, - ), - ), - ), - ); + ], + ], + 'relations' => [ + $linkName => [ + 'conditions' => [ + 'OR' => [ + ['field' => null], + ['field' => $linkName], + ], + ], + ], + ], + ], + ]; $relation = \Espo\Core\Utils\Util::merge($parentRelation, $relation); return $relation; } } - diff --git a/application/Espo/Core/Utils/FieldManager/Hooks/AttachmentMultipleType.php b/application/Espo/Core/Utils/FieldManager/Hooks/AttachmentMultipleType.php deleted file mode 100644 index e57770c5b2..0000000000 --- a/application/Espo/Core/Utils/FieldManager/Hooks/AttachmentMultipleType.php +++ /dev/null @@ -1,48 +0,0 @@ -getMetadata()->get(['entityDefs', $scope, 'fields'], array()); - foreach ($fieldDefs as $field => $defs) { - $type = $this->getMetadata()->get(['entityDefs', $scope, 'fields', $field, 'type']); - if ($type === 'attachmentMultiple') { - throw new Conflict("Attachment-Multiple field already exists in '{$scope}'. There can be only one Attachment-Multiple field per entity type."); - } - } - } - } -} \ No newline at end of file diff --git a/application/Espo/ORM/DB/Mapper.php b/application/Espo/ORM/DB/Mapper.php index 12a153438f..51f4f9a9f1 100644 --- a/application/Espo/ORM/DB/Mapper.php +++ b/application/Espo/ORM/DB/Mapper.php @@ -232,6 +232,10 @@ abstract class Mapper implements IMapper $params['limit'] = 1; } + if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { + $params['whereClause'][] = $relDefs['conditions']; + } + $resultArr = []; $sql = $this->query->createSelectQuery($relEntity->getEntityType(), $params); @@ -1005,16 +1009,14 @@ abstract class Mapper implements IMapper . " AND " . "{$relTable}.deleted = " . $this->pdo->quote(0) . ""; + $conditions = $conditions ?? []; if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { - foreach ($relDefs['conditions'] as $f => $v) { - $join .= " AND {$relTable}." . $this->toDb($f) . " = " . $this->pdo->quote($v); - } + $conditions = array_merge($conditions, $relDefs['conditions']); } - if (!empty($conditions) && is_array($conditions)) { - foreach ($conditions as $f => $v) { - $join .= " AND {$relTable}." . $this->toDb($f) . " = " . $this->pdo->quote($v); - } + if (!empty($conditions)) { + $conditionsSql = $this->query->buildJoinConditionsStatement($entity, $relTable, $conditions); + $join .= " AND " . $conditionsSql; } return $join; diff --git a/application/Espo/ORM/DB/Query/Base.php b/application/Espo/ORM/DB/Query/Base.php index 91b585680f..c10d9c61fd 100644 --- a/application/Espo/ORM/DB/Query/Base.php +++ b/application/Espo/ORM/DB/Query/Base.php @@ -1638,10 +1638,41 @@ abstract class Base return implode(' ', $joinSqlList); } + public function buildJoinConditionsStatement($entity, $alias = null, array $conditions) + { + $sql = ''; + + $joinSqlList = []; + foreach ($conditions as $left => $right) { + $joinSqlList[] = $this->buildJoinConditionStatement($entity, $alias, $left, $right); + } + if (count($joinSqlList)) { + $sql .= implode(" AND ", $joinSqlList); + } + + return $sql; + } + protected function buildJoinConditionStatement($entity, $alias = null, $left, $right) { $sql = ''; + if (is_array($right) && (is_int($left) || in_array($left, ['AND', 'OR']))) { + $logicalOperator = 'AND'; + if ($left == 'OR') { + $logicalOperator = 'OR'; + } + + $sqlList = []; + foreach ($right as $k => $v) { + $sqlList[] = $this->buildJoinConditionStatement($entity, $alias, $k, $v); + } + + $sql = implode(' ' .$logicalOperator . ' ', $sqlList); + + return $sql; + } + $operator = '='; $isNotValue = false; @@ -1748,6 +1779,10 @@ abstract class Base $alias = $this->sanitize($alias); + if (!empty($relOpt['conditions']) && is_array($relOpt['conditions'])) { + $conditions = array_merge($conditions, $relOpt['conditions']); + } + $type = $relOpt['type']; switch ($type) { @@ -1769,10 +1804,6 @@ abstract class Base . " AND " . "{$midAlias}.deleted = " . $this->pdo->quote(0); - if (!empty($relOpt['conditions']) && is_array($relOpt['conditions'])) { - $conditions = array_merge($conditions, $relOpt['conditions']); - } - $joinSqlList = []; foreach ($conditions as $left => $right) { $joinSqlList[] = $this->buildJoinConditionStatement($entity, $midAlias, $left, $right); @@ -1797,7 +1828,6 @@ abstract class Base . " AND " . "{$alias}.deleted = " . $this->pdo->quote(0) . ""; - $joinSqlList = []; foreach ($conditions as $left => $right) { $joinSqlList[] = $this->buildJoinConditionStatement($entity, $alias, $left, $right); diff --git a/tests/unit/Espo/ORM/DB/QueryTest.php b/tests/unit/Espo/ORM/DB/QueryTest.php index a3faac03da..135fe2ad7f 100644 --- a/tests/unit/Espo/ORM/DB/QueryTest.php +++ b/tests/unit/Espo/ORM/DB/QueryTest.php @@ -256,6 +256,24 @@ class QueryTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expectedSql, $sql); } + public function testJoinConditions3() + { + $sql = $this->query->createSelectQuery('Note', [ + 'select' => ['id'], + 'leftJoins' => [['post', 'post', [ + 'OR' => [ + ['name' => 'test'], + ['post.name' => null], + ] + ]]], + 'withDeleted' => true, + ]); + + $expectedSql = "SELECT note.id AS `id` FROM `note` LEFT JOIN `post` AS `post` ON post.name = 'test' OR post.name IS NULL"; + + $this->assertEquals($expectedSql, $sql); + } + public function testJoinTable() { $sql = $this->query->createSelectQuery('Post', [