diff --git a/application/Espo/Core/ApplicationRunners/Rebuild.php b/application/Espo/Core/ApplicationRunners/Rebuild.php index 2c44d4afc9..3cfef0c1ff 100644 --- a/application/Espo/Core/ApplicationRunners/Rebuild.php +++ b/application/Espo/Core/ApplicationRunners/Rebuild.php @@ -31,6 +31,7 @@ namespace Espo\Core\ApplicationRunners; use Espo\Core\Application\Runner; use Espo\Core\DataManager; +use Espo\Core\Utils\Log; use Exception; /** @@ -40,7 +41,7 @@ class Rebuild implements Runner { use Cli; - public function __construct(private DataManager $dataManager) + public function __construct(private DataManager $dataManager, private Log $log) {} public function run(): void @@ -51,6 +52,11 @@ class Rebuild implements Runner catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; + $this->log->error('Rebuild: ' . $e->getMessage(), [ + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]); + exit(1); } } diff --git a/application/Espo/Core/Utils/Database/Orm/Base.php b/application/Espo/Core/Utils/Database/Orm/Base.php deleted file mode 100644 index 013bc38d26..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Base.php +++ /dev/null @@ -1,291 +0,0 @@ - - */ - private $ormEntityDefs; - - /** - * @var array - */ - private $entityDefs; - - protected Config $config; - - /** - * @param array $ormEntityDefs - * @param array $entityDefs - */ - public function __construct(Metadata $metadata, array $ormEntityDefs, array $entityDefs, Config $config) - { - $this->metadata = $metadata; - $this->ormEntityDefs = $ormEntityDefs; - $this->entityDefs = $entityDefs; - $this->config = $config; - } - - protected function getMetadata(): Metadata - { - return $this->metadata; - } - - /** - * @return array - */ - protected function getOrmEntityDefs() - { - return $this->ormEntityDefs; - } - - /** - * @return array - */ - protected function getEntityDefs() - { - return $this->entityDefs; - } - - /** - * Set current Field name or Link name. - * - * @param string $itemName - * @return void - */ - protected function setItemName($itemName) - { - $this->itemName = $itemName; - } - - /** - * Get current Field name. - * - * @return string - */ - protected function getFieldName() - { - if ($this->itemName === null) { - throw new RuntimeException("No item-name."); - } - - return $this->itemName; - } - - /** - * Get current Link name. - * - * @return string - */ - protected function getLinkName() - { - if ($this->itemName === null) { - throw new RuntimeException("No item-name."); - } - - return $this->itemName; - } - - /** - * Set current Entity Name. - * - * @param string $entityName - * @return void - */ - protected function setEntityName($entityName) - { - $this->entityName = $entityName; - } - - /** - * Get current Entity Name. - * - * @return string - */ - protected function getEntityName() - { - if ($this->entityName === null) { - throw new RuntimeException("No entity-name."); - } - - return $this->entityName; - } - - /** - * @todo Call methods explicitly. - * - * @param array $keyValueList - * @return void - */ - protected function setMethods(array $keyValueList) - { - foreach ($keyValueList as $key => $value) { - $methodName = 'set' . ucfirst($key); - - if (method_exists($this, $methodName)) { - $this->$methodName($value); - } - } - } - - /** - * Get Entity Defs by type (entity/orm). - * - * @param bool $isOrmEntityDefs - * @return array - */ - protected function getDefs($isOrmEntityDefs = false) - { - $entityDefs = $isOrmEntityDefs ? $this->getOrmEntityDefs() : $this->getEntityDefs(); - - return $entityDefs; - } - - /** - * Get entity params by name. - * - * @param string $entityName - * @param bool $isOrmEntityDefs - * @param mixed $returns - * @return mixed - */ - protected function getEntityParams($entityName = null, $isOrmEntityDefs = false, $returns = null) - { - if (!isset($entityName)) { - $entityName = $this->getEntityName(); - } - - $entityDefs = $this->getDefs($isOrmEntityDefs); - - if (isset($entityDefs[$entityName])) { - return $entityDefs[$entityName]; - } - - return $returns; - } - - /** - * Get field params by name for a specified entity. - * - * @param string $fieldName - * @param string $entityName - * @param bool $isOrmEntityDefs - * @param mixed $returns - * @return mixed - */ - protected function getFieldParams($fieldName = null, $entityName = null, $isOrmEntityDefs = false, $returns = null) - { - if (!isset($fieldName)) { - $fieldName = $this->getFieldName(); - } - if (!isset($entityName)) { - $entityName = $this->getEntityName(); - } - - $entityDefs = $this->getDefs($isOrmEntityDefs); - - if (isset($entityDefs[$entityName]) && isset($entityDefs[$entityName]['fields'][$fieldName])) { - return $entityDefs[$entityName]['fields'][$fieldName]; - } - - return $returns; - } - - /** - * Get relation params by name for a specified entity. - * - * @param string $linkName - * @param string $entityName - * @param bool $isOrmEntityDefs - * @param mixed $returns - * @return mixed - */ - protected function getLinkParams($linkName = null, $entityName = null, $isOrmEntityDefs = false, $returns = null) - { - if (!isset($linkName)) { - $linkName = $this->getLinkName(); - } - if (!isset($entityName)) { - $entityName = $this->getEntityName(); - } - - $entityDefs = $this->getDefs($isOrmEntityDefs); - $relationKeyName = $isOrmEntityDefs ? 'relations' : 'links'; - - if (isset($entityDefs[$entityName]) && isset($entityDefs[$entityName][$relationKeyName][$linkName])) { - return $entityDefs[$entityName][$relationKeyName][$linkName]; - } - - return $returns; - } - - /** - * @return string - */ - protected function getForeignField(string $name, string $entityType) - { - return $name; - } - - /** - * Set a value for all elements of array. So, in result all elements will have the same values. - * - * @param mixed $inputValue - * @param mixed[] $array - * @return mixed[] - */ - protected function setArrayValue($inputValue, array $array) - { - foreach ($array as &$value) { - $value = $inputValue; - } - - return $array; - } -} diff --git a/application/Espo/Core/Utils/Database/Orm/Converter.php b/application/Espo/Core/Utils/Database/Orm/Converter.php index d08f4520a6..6193f3c696 100644 --- a/application/Espo/Core/Utils/Database/Orm/Converter.php +++ b/application/Espo/Core/Utils/Database/Orm/Converter.php @@ -39,9 +39,7 @@ use Espo\ORM\Defs\IndexDefs; use Espo\ORM\Defs\RelationDefs; use Espo\ORM\Entity; use Espo\Core\Utils\Metadata; -use Espo\Core\Utils\Config; use Espo\Core\Utils\Metadata\Helper as MetadataHelper; -use ReflectionClass; class Converter { @@ -109,7 +107,6 @@ class Converter public function __construct( private Metadata $metadata, - private Config $config, private RelationConverter $relationConverter, private MetadataHelper $metadataHelper, private InjectableFactory $injectableFactory, @@ -293,7 +290,7 @@ class Converter default: $constName = strtoupper(Util::toUnderScore($attributeType)); - if (!defined('Espo\\ORM\\Entity::' . $constName)) { + if (!defined('Espo\\ORM\\Type\\AttributeType::' . $constName)) { $attributeParams['type'] = $this->defaultAttributeType; } @@ -436,8 +433,6 @@ class Converter */ private function correctFields(string $entityType, array $ormMetadata): array { - $entityDefs = $this->getEntityDefs(); - $entityMetadata = $ormMetadata[$entityType]; foreach ($entityMetadata['fields'] as $field => $fieldParams) { @@ -447,23 +442,10 @@ class Converter continue; } + /** @var ?class-string $className */ $className = $this->metadata->get(['fields', $fieldType, 'converterClassName']); - if (!$className) { - // Legacy. - $className = 'Espo\Custom\Core\Utils\Database\Orm\Fields\\' . ucfirst($fieldType); - - if (!class_exists($className)) { - $className = 'Espo\Core\Utils\Database\Orm\Fields\\' . ucfirst($fieldType); - } - } - - if ( - class_exists($className) && - (new ReflectionClass($className))->implementsInterface(FieldConverter::class) - ) { - /** @var class-string $className */ - + if ($className) { $toUnset = !in_array('', $this->metadata->get(['fields', $fieldType, 'actualFields']) ?? []) && !in_array('', $this->metadata->get(['fields', $fieldType, 'notActualFields']) ?? []); @@ -483,33 +465,6 @@ class Converter /** @var array $ormMetadata */ $ormMetadata = Util::merge($ormMetadata, [$entityType => $convertedEntityDefs->toAssoc()]); - - // @todo Unset if needed. - - $className = null; - } - - if ( - $className && - class_exists($className) && - method_exists($className, 'load') && - method_exists($className, 'process') - ) { - // Legacy. - $helperClass = new $className($this->metadata, $ormMetadata, $entityDefs, $this->config); - - assert(method_exists($helperClass, 'process')); - - $fieldResult = $helperClass->process($field, $entityType); - - if (isset($fieldResult['unset'])) { - $ormMetadata = Util::unsetInArray($ormMetadata, $fieldResult['unset']); - - unset($fieldResult['unset']); - } - - /** @var array $ormMetadata */ - $ormMetadata = Util::merge($ormMetadata, $fieldResult); } $defaultAttributes = $this->metadata @@ -520,7 +475,7 @@ class Converter $entityType => [ 'fields' => [ $field => [ - 'default' => $defaultAttributes[$field] + 'default' => $defaultAttributes[$field], ] ] ] diff --git a/application/Espo/Core/Utils/Database/Orm/Defs/RelationDefs.php b/application/Espo/Core/Utils/Database/Orm/Defs/RelationDefs.php index c59b2313de..7369ba3cb5 100644 --- a/application/Espo/Core/Utils/Database/Orm/Defs/RelationDefs.php +++ b/application/Espo/Core/Utils/Database/Orm/Defs/RelationDefs.php @@ -92,7 +92,7 @@ class RelationDefs /** * Clone with a foreign relation name. */ - public function withForeignRelationName(string $name): self + public function withForeignRelationName(?string $name): self { return $this->withParam('foreign', $name); } @@ -202,7 +202,7 @@ class RelationDefs /** * Clone with conditions. Conditions are used for relationships that share a same middle table. * - * @param array $conditions + * @param array)|null> $conditions */ public function withConditions(array $conditions): self { diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/HasManyHasMany.php b/application/Espo/Core/Utils/Database/Orm/LinkConverter.php similarity index 82% rename from application/Espo/Core/Utils/Database/Orm/Relations/HasManyHasMany.php rename to application/Espo/Core/Utils/Database/Orm/LinkConverter.php index 6511da071e..ec7b5c0468 100644 --- a/application/Espo/Core/Utils/Database/Orm/Relations/HasManyHasMany.php +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverter.php @@ -27,9 +27,15 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Utils\Database\Orm\Relations; +namespace Espo\Core\Utils\Database\Orm; -class HasManyHasMany extends ManyMany +use Espo\ORM\Defs\RelationDefs; +use Espo\Core\Utils\Database\Orm\Defs\EntityDefs; + +/** + * Converts link definitions to ORM definitions. + */ +interface LinkConverter { - + public function convert(RelationDefs $linkDefs, string $entityType): EntityDefs; } diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/EmailEmailAddress.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/Attachments.php similarity index 54% rename from application/Espo/Core/Utils/Database/Orm/Relations/EmailEmailAddress.php rename to application/Espo/Core/Utils/Database/Orm/LinkConverters/Attachments.php index 55c8d43d21..81786d4673 100644 --- a/application/Espo/Core/Utils/Database/Orm/Relations/EmailEmailAddress.php +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/Attachments.php @@ -27,41 +27,44 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Utils\Database\Orm\Relations; +namespace Espo\Core\Utils\Database\Orm\LinkConverters; -use RuntimeException; +use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs; +use Espo\Core\Utils\Database\Orm\Defs\EntityDefs; +use Espo\Core\Utils\Database\Orm\LinkConverter; +use Espo\ORM\Defs\RelationDefs as LinkDefs; +use Espo\ORM\Type\AttributeType; +use LogicException; -class EmailEmailAddress extends HasMany +class Attachments implements LinkConverter { - /** - * @param string $linkName - * @param string $entityName - * @return array - */ - protected function load($linkName, $entityName) + public function __construct(private HasChildren $hasChildren) {} + + public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs { - $parentRelation = parent::load($linkName, $entityName); + $name = $linkDefs->getName(); - $foreignEntityName = $this->getForeignEntityName(); + $entityDefs = $this->hasChildren->convert($linkDefs, $entityType); - if ($foreignEntityName === null) { - throw new RuntimeException("No foreign-entity-type."); - } - - $relation = array( - $entityName => array( - 'relations' => array( - $linkName => array( - 'midKeys' => array( - lcfirst($entityName).'Id', - lcfirst($foreignEntityName).'Id', - ), - ), - ), - ), + $entityDefs = $entityDefs->withAttribute( + AttributeDefs::create($name . 'Types') + ->withType(AttributeType::JSON_OBJECT) + ->withNotStorable() ); - /** @var array */ - return \Espo\Core\Utils\Util::merge($parentRelation, $relation); + $relationDefs = $entityDefs->getRelation($name); + + if (!$relationDefs) { + throw new LogicException(); + } + + $relationDefs = $relationDefs->withConditions([ + 'OR' => [ + ['field' => null], + ['field' => $name], + ] + ]); + + return $entityDefs->withRelation($relationDefs); } } diff --git a/application/Espo/Core/Utils/Database/Orm/LinkConverters/BelongsTo.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/BelongsTo.php new file mode 100644 index 0000000000..d46904d3b9 --- /dev/null +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/BelongsTo.php @@ -0,0 +1,91 @@ +getName(); + $foreignEntityType = $linkDefs->getForeignEntityType(); + $foreignRelationName = $linkDefs->hasForeignRelationName() ? $linkDefs->getForeignRelationName() : null; + $noIndex = $linkDefs->getParam('noIndex'); + $noForeignName = $linkDefs->getParam('noForeignName'); + $foreignName = $linkDefs->getParam('foreignName') ?? 'name'; + $noJoin = $linkDefs->getParam('noJoin'); + + $idName = $name . 'Id'; + $nameName = $name . 'Name'; + + $idAttributeDefs = AttributeDefs::create($idName) + ->withType(AttributeType::FOREIGN_ID) + ->withParam('index', !$noIndex); + + $relationDefs = RelationDefs::create($name) + ->withType(RelationType::BELONGS_TO) + ->withForeignEntityType($foreignEntityType) + ->withKey($idName) + ->withForeignKey('id') + ->withForeignRelationName($foreignRelationName); + + $nameAttributeDefs = !$noForeignName ? + ( + $noJoin ? + AttributeDefs::create($nameName) + ->withType(AttributeType::VARCHAR) + ->withNotStorable() + ->withParam('relation', $name) + ->withParam('foreign', $foreignName) : + AttributeDefs::create($nameName) + ->withType(AttributeType::FOREIGN) + ->withNotStorable(true) // Used to be false before v7.4. + ->withParam('relation', $name) + ->withParam('foreign', $foreignName) + ) : null; + + $entityDefs = EntityDefs::create() + ->withAttribute($idAttributeDefs) + ->withRelation($relationDefs); + + if ($nameAttributeDefs) { + $entityDefs = $entityDefs->withAttribute($nameAttributeDefs); + } + + return $entityDefs; + } +} diff --git a/application/Espo/Core/Utils/Database/Orm/LinkConverters/BelongsToParent.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/BelongsToParent.php new file mode 100644 index 0000000000..d357548f00 --- /dev/null +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/BelongsToParent.php @@ -0,0 +1,80 @@ +getName(); + + $foreignRelationName = $linkDefs->hasForeignRelationName() ? + $linkDefs->getForeignRelationName() : null; + + $idName = $name . 'Id'; + $nameName = $name . 'Name'; + $typeName = $name . 'Type'; + + $relationDefs = RelationDefs::create($name) + ->withType(RelationType::BELONGS_TO_PARENT) + ->withKey($idName) + ->withForeignRelationName($foreignRelationName); + + return EntityDefs::create() + ->withAttribute( + AttributeDefs::create($idName) + ->withType(AttributeType::FOREIGN_ID) + ->withParam('index', $name) + ) + ->withAttribute( + AttributeDefs::create($typeName) + ->withType(AttributeType::FOREIGN_TYPE) + ->withParam('notNull', false) // Revise whether needed. + ->withParam('index', $name) + ->withLength(self::TYPE_LENGTH) + ) + ->withAttribute( + AttributeDefs::create($nameName) + ->withType(AttributeType::VARCHAR) + ->withNotStorable() + ) + ->withRelation($relationDefs); + } +} diff --git a/application/Espo/Core/Utils/Database/Orm/LinkConverters/EmailEmailAddress.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/EmailEmailAddress.php new file mode 100644 index 0000000000..3c172b19ab --- /dev/null +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/EmailEmailAddress.php @@ -0,0 +1,77 @@ +getName(); + $hasField = $linkDefs->getParam('hasField'); + + $foreignEntityType = EmailAddress::ENTITY_TYPE; + + $key1 = lcfirst($entityType) . 'Id'; + $key2 = lcfirst($foreignEntityType) . 'Id'; + + $relationDefs = RelationDefs::create($name) + ->withType(RelationType::MANY_MANY) + ->withForeignEntityType($foreignEntityType) + ->withKey('id') + ->withForeignKey('id') + ->withMidKeys($key1, $key2); + + return EntityDefs::create() + ->withAttribute( + AttributeDefs::create($name . 'Ids') + ->withType(AttributeType::JSON_ARRAY) + ->withNotStorable() + ->withParam('isLinkStub', !$hasField) + ) + ->withAttribute( + AttributeDefs::create($name . 'Names') + ->withType(AttributeType::JSON_OBJECT) + ->withNotStorable() + ->withParam('isLinkStub', !$hasField) + ) + ->withRelation($relationDefs); + } +} diff --git a/application/Espo/Core/Utils/Database/Orm/Fields/Base.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/EntityTeam.php similarity index 50% rename from application/Espo/Core/Utils/Database/Orm/Fields/Base.php rename to application/Espo/Core/Utils/Database/Orm/LinkConverters/EntityTeam.php index a487817639..c2518f594c 100644 --- a/application/Espo/Core/Utils/Database/Orm/Fields/Base.php +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/EntityTeam.php @@ -27,44 +27,39 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Utils\Database\Orm\Fields; +namespace Espo\Core\Utils\Database\Orm\LinkConverters; -/** - * @deprecated As of v7.4. Use FieldConverter. - */ -class Base extends \Espo\Core\Utils\Database\Orm\Base +use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs; +use Espo\Core\Utils\Database\Orm\Defs\EntityDefs; +use Espo\Core\Utils\Database\Orm\Defs\RelationDefs; +use Espo\Core\Utils\Database\Orm\LinkConverter; +use Espo\Entities\Team; +use Espo\ORM\Defs\RelationDefs as LinkDefs; +use Espo\ORM\Type\AttributeType; +use Espo\ORM\Type\RelationType; + +class EntityTeam implements LinkConverter { - /** - * ORM conversion for fields. - * - * @param string $itemName A field name. - * @param string $entityName - * @return array - */ - public function process($itemName, $entityName) + private const ENTITY_TYPE_LENGTH = 100; + + public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs { - $inputs = [ - 'itemName' => $itemName, - 'entityName' => $entityName, - ]; + $name = $linkDefs->getName(); + $relationshipName = $linkDefs->getRelationshipName(); - $this->setMethods($inputs); - - $convertedDefs = $this->load($itemName, $entityName); - - $inputs = $this->setArrayValue(null, $inputs); - $this->setMethods($inputs); - - return $convertedDefs; - } - - /** - * @param string $fieldName - * @param string $entityType - * @return array - */ - protected function load($fieldName, $entityType) - { - return []; + return EntityDefs::create() + ->withRelation( + RelationDefs::create($name) + ->withType(RelationType::MANY_MANY) + ->withForeignEntityType(Team::ENTITY_TYPE) + ->withRelationshipName($relationshipName) + ->withMidKeys('entityId', 'teamId') + ->withConditions(['entityType' => $entityType]) + ->withAdditionalColumn( + AttributeDefs::create('entityType') + ->withType(AttributeType::VARCHAR) + ->withLength(self::ENTITY_TYPE_LENGTH) + ) + ); } } diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/Attachments.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/EntityUser.php similarity index 50% rename from application/Espo/Core/Utils/Database/Orm/Relations/Attachments.php rename to application/Espo/Core/Utils/Database/Orm/LinkConverters/EntityUser.php index b9178275e2..b2cbbc2ec0 100644 --- a/application/Espo/Core/Utils/Database/Orm/Relations/Attachments.php +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/EntityUser.php @@ -27,43 +27,39 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Utils\Database\Orm\Relations; +namespace Espo\Core\Utils\Database\Orm\LinkConverters; -use Espo\Core\Utils\Util; +use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs; +use Espo\Core\Utils\Database\Orm\Defs\EntityDefs; +use Espo\Core\Utils\Database\Orm\Defs\RelationDefs; +use Espo\Core\Utils\Database\Orm\LinkConverter; +use Espo\Entities\User; +use Espo\ORM\Defs\RelationDefs as LinkDefs; +use Espo\ORM\Type\AttributeType; +use Espo\ORM\Type\RelationType; -class Attachments extends HasChildren +class EntityUser implements LinkConverter { - /** - * @param string $linkName - * @param string $entityName - * @return array - */ - protected function load($linkName, $entityName) + private const ENTITY_TYPE_LENGTH = 100; + + public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs { - $parentRelation = parent::load($linkName, $entityName); + $name = $linkDefs->getName(); + $relationshipName = $linkDefs->getRelationshipName(); - $relation = [ - $entityName => [ - 'fields' => [ - $linkName.'Types' => [ - 'type' => 'jsonObject', - 'notStorable' => true, - ], - ], - 'relations' => [ - $linkName => [ - 'conditions' => [ - 'OR' => [ - ['field' => null], - ['field' => $linkName], - ], - ], - ], - ], - ], - ]; - - /** @var array */ - return Util::merge($parentRelation, $relation); + return EntityDefs::create() + ->withRelation( + RelationDefs::create($name) + ->withType(RelationType::MANY_MANY) + ->withForeignEntityType(User::ENTITY_TYPE) + ->withRelationshipName($relationshipName) + ->withMidKeys('entityId', 'userId') + ->withConditions(['entityType' => $entityType]) + ->withAdditionalColumn( + AttributeDefs::create('entityType') + ->withType(AttributeType::VARCHAR) + ->withLength(self::ENTITY_TYPE_LENGTH) + ) + ); } } diff --git a/application/Espo/Core/Utils/Database/Orm/LinkConverters/HasChildren.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/HasChildren.php new file mode 100644 index 0000000000..dcf63cd61f --- /dev/null +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/HasChildren.php @@ -0,0 +1,71 @@ +getName(); + $foreignEntityType = $linkDefs->getForeignEntityType(); + $foreignRelationName = $linkDefs->hasForeignRelationName() ? $linkDefs->getForeignRelationName() : null; + $hasField = $linkDefs->getParam('hasField'); + + $relationDefs = RelationDefs::create($name) + ->withType(RelationType::HAS_CHILDREN) + ->withForeignEntityType($foreignEntityType) + ->withForeignKey($foreignRelationName . 'Id') + ->withParam('foreignType', $foreignRelationName . 'Type') + ->withForeignRelationName($foreignRelationName); + + return EntityDefs::create() + ->withAttribute( + AttributeDefs::create($name . 'Ids') + ->withType(AttributeType::JSON_ARRAY) + ->withNotStorable() + ->withParam('isLinkStub', !$hasField) // Revise. Change to notExportable? + ) + ->withAttribute( + AttributeDefs::create($name . 'Names') + ->withType(AttributeType::JSON_OBJECT) + ->withNotStorable() + ->withParam('isLinkStub', !$hasField) + ) + ->withRelation($relationDefs); + } +} diff --git a/application/Espo/Core/Utils/Database/Orm/LinkConverters/HasMany.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/HasMany.php new file mode 100644 index 0000000000..bdf06c19af --- /dev/null +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/HasMany.php @@ -0,0 +1,74 @@ +getName(); + $foreignEntityType = $linkDefs->getForeignEntityType(); + $foreignRelationName = $linkDefs->getForeignRelationName(); + $hasField = $linkDefs->getParam('hasField'); + + $type = $linkDefs->hasRelationshipName() ? + RelationType::MANY_MANY : // Revise. + RelationType::HAS_MANY; + + return EntityDefs::create() + ->withAttribute( + AttributeDefs::create($name . 'Ids') + ->withType(AttributeType::JSON_ARRAY) + ->withNotStorable() + ->withParam('isLinkStub', !$hasField) // Revise. Change to notExportable? + ) + ->withAttribute( + AttributeDefs::create($name . 'Names') + ->withType(AttributeType::JSON_OBJECT) + ->withNotStorable() + ->withParam('isLinkStub', !$hasField) + ) + ->withRelation( + RelationDefs::create($name) + ->withType($type) + ->withForeignEntityType($foreignEntityType) + ->withForeignKey($foreignRelationName . 'Id') + ->withForeignRelationName($foreignRelationName) + ); + } +} diff --git a/application/Espo/Core/Utils/Database/Orm/LinkConverters/HasOne.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/HasOne.php new file mode 100644 index 0000000000..6bbe1e2190 --- /dev/null +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/HasOne.php @@ -0,0 +1,89 @@ +getName(); + $foreignEntityType = $linkDefs->getForeignEntityType(); + $foreignRelationName = $linkDefs->hasForeignRelationName() ? $linkDefs->getForeignRelationName() : null; + $noForeignName = $linkDefs->getParam('noForeignName'); + $foreignName = $linkDefs->getParam('foreignName') ?? 'name'; + $noJoin = $linkDefs->getParam('noJoin'); + + $idName = $name . 'Id'; + $nameName = $name . 'Name'; + + $idAttributeDefs = AttributeDefs::create($idName) + ->withType($noJoin ? AttributeType::VARCHAR : AttributeType::FOREIGN) + ->withNotStorable() + ->withParam('relation', $name) + ->withParam('foreign', 'id'); + + $nameAttributeDefs = !$noForeignName ? + ( + AttributeDefs::create($nameName) + ->withType($noJoin ? AttributeType::VARCHAR : AttributeType::FOREIGN) + ->withNotStorable() + ->withParam('relation', $name) + ->withParam('foreign', $foreignName) + ) : null; + + $relationDefs = RelationDefs::create($name) + ->withType(RelationType::HAS_ONE) + ->withForeignEntityType($foreignEntityType); + + if ($foreignRelationName) { + $relationDefs = $relationDefs + ->withForeignKey($foreignRelationName . 'Id') + ->withForeignRelationName($foreignRelationName); + } + + $entityDefs = EntityDefs::create() + ->withAttribute($idAttributeDefs) + ->withRelation($relationDefs); + + if ($nameAttributeDefs) { + $entityDefs = $entityDefs->withAttribute($nameAttributeDefs); + } + + return $entityDefs; + } +} diff --git a/application/Espo/Core/Utils/Database/Orm/LinkConverters/ManyMany.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/ManyMany.php new file mode 100644 index 0000000000..596cd12867 --- /dev/null +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/ManyMany.php @@ -0,0 +1,104 @@ +getName(); + $foreignEntityType = $linkDefs->getForeignEntityType(); + $foreignRelationName = $linkDefs->getForeignRelationName(); + $hasField = $linkDefs->getParam('hasField'); + $columnAttributeMap = $linkDefs->getParam('columnAttributeMap'); + + $relationshipName = $linkDefs->hasRelationshipName() ? + $linkDefs->getRelationshipName() : + self::composeRelationshipName($entityType, $foreignEntityType); + + $key1 = lcfirst($entityType) . 'Id'; + $key2 = lcfirst($foreignEntityType) . 'Id'; + + if ($key1 === $key2) { + [$key1, $key2] = strcmp($name, $foreignRelationName) ? + ['leftId', 'rightId'] : + ['rightId', 'leftId']; + } + + $relationDefs = RelationDefs::create($name) + ->withType(RelationType::MANY_MANY) + ->withForeignEntityType($foreignEntityType) + ->withRelationshipName($relationshipName) + ->withKey('id') + ->withForeignKey('id') + ->withMidKeys($key1, $key2) + ->withForeignRelationName($foreignRelationName); + + if ($columnAttributeMap) { + $relationDefs = $relationDefs->withParam('columnAttributeMap', $columnAttributeMap); + } + + return EntityDefs::create() + ->withAttribute( + AttributeDefs::create($name . 'Ids') + ->withType(AttributeType::JSON_ARRAY) + ->withNotStorable() + ->withParam('isLinkStub', !$hasField) // Revise. + ) + ->withAttribute( + AttributeDefs::create($name . 'Names') + ->withType(AttributeType::JSON_OBJECT) + ->withNotStorable() + ->withParam('isLinkStub', !$hasField) // Revise. + ) + ->withRelation($relationDefs); + } + + private static function composeRelationshipName(string $left, string $right): string + { + $parts = [ + Util::toCamelCase(lcfirst($left)), + Util::toCamelCase(lcfirst($right)), + ]; + + sort($parts); + + return Util::toCamelCase(implode('_', $parts)); + } +} diff --git a/application/Espo/Core/Utils/Database/Orm/LinkConverters/SmsPhoneNumber.php b/application/Espo/Core/Utils/Database/Orm/LinkConverters/SmsPhoneNumber.php new file mode 100644 index 0000000000..50919aa2de --- /dev/null +++ b/application/Espo/Core/Utils/Database/Orm/LinkConverters/SmsPhoneNumber.php @@ -0,0 +1,73 @@ +getName(); + $foreignEntityType = PhoneNumber::ENTITY_TYPE; + + $key1 = lcfirst($entityType) . 'Id'; + $key2 = lcfirst($foreignEntityType) . 'Id'; + + $relationDefs = RelationDefs::create($name) + ->withType(RelationType::MANY_MANY) + ->withForeignEntityType($foreignEntityType) + ->withKey('id') + ->withForeignKey('id') + ->withMidKeys($key1, $key2); + + return EntityDefs::create() + ->withAttribute( + AttributeDefs::create($name . 'Ids') + ->withType(AttributeType::JSON_ARRAY) + ->withNotStorable() + ) + ->withAttribute( + AttributeDefs::create($name . 'Names') + ->withType(AttributeType::JSON_OBJECT) + ->withNotStorable() + ) + ->withRelation($relationDefs); + } +} diff --git a/application/Espo/Core/Utils/Database/Orm/RelationConverter.php b/application/Espo/Core/Utils/Database/Orm/RelationConverter.php index 726e4f78b7..d3abe0bc3a 100644 --- a/application/Espo/Core/Utils/Database/Orm/RelationConverter.php +++ b/application/Espo/Core/Utils/Database/Orm/RelationConverter.php @@ -29,118 +29,212 @@ namespace Espo\Core\Utils\Database\Orm; +use Espo\Core\InjectableFactory; +use Espo\Core\Utils\Database\Orm\LinkConverters\BelongsTo; +use Espo\Core\Utils\Database\Orm\LinkConverters\BelongsToParent; +use Espo\Core\Utils\Database\Orm\LinkConverters\HasChildren; +use Espo\Core\Utils\Database\Orm\LinkConverters\HasMany; +use Espo\Core\Utils\Database\Orm\LinkConverters\HasOne; +use Espo\Core\Utils\Database\Orm\LinkConverters\ManyMany; use Espo\Core\Utils\Util; use Espo\Core\Utils\Metadata; -use Espo\Core\Utils\Config; +use Espo\ORM\Defs\RelationDefs; +use Espo\ORM\Type\AttributeType; +use Espo\ORM\Type\RelationType; +use RuntimeException; class RelationConverter { + private const DEFAULT_VARCHAR_LENGTH = 255; + + /** @var string[] */ + private $allowedParams = [ + 'relationName', + 'conditions', + 'additionalColumns', + 'midKeys', + 'noJoin', + 'indexes', + ]; + public function __construct( private Metadata $metadata, - private Config $config + private InjectableFactory $injectableFactory ) {} /** - * @param string $relationName - */ - private function relationExists($relationName): bool - { - if ($this->getRelationClass($relationName) !== false) { - return true; - } - - return false; - } - - /** - * @param string $relationName - * @return class-string<\Espo\Core\Utils\Database\Orm\Relations\Base>|false - */ - private function getRelationClass($relationName) - { - $relationName = ucfirst($relationName); - - $className = 'Espo\Custom\Core\Utils\Database\Orm\Relations\\' . $relationName; - - if (!class_exists($className)) { - $className = 'Espo\Core\Utils\Database\Orm\Relations\\' . $relationName; - } - - if (class_exists($className)) { - /** @var class-string<\Espo\Core\Utils\Database\Orm\Relations\Base> */ - return $className; - } - - return false; - } - - /** - * Get a foreign link. - * - * @param array $parentLinkParams - * @param array $currentEntityDefs - * @return array{name: string, params: array}|false - */ - private function getForeignLink($parentLinkParams, $currentEntityDefs) - { - if (isset($parentLinkParams['foreign']) && isset($currentEntityDefs['links'][$parentLinkParams['foreign']])) { - return [ - 'name' => $parentLinkParams['foreign'], - 'params' => $currentEntityDefs['links'][$parentLinkParams['foreign']], - ]; - } - - return false; - } - - /** - * @param string $linkName - * @param array $linkParams + * @param string $name + * @param array $params * @param string $entityType * @param array $ormMetadata * @return ?array */ - public function process(string $linkName, array $linkParams, string $entityType, array $ormMetadata): ?array + public function process(string $name, array $params, string $entityType, array $ormMetadata): ?array { - $entityDefs = $this->metadata->get('entityDefs'); + $foreignEntityType = $params['entity'] ?? null; + $foreignLinkName = $params['foreign'] ?? null; - $foreignEntityType = $linkParams['entity'] ?? $entityType; - $foreignLink = $this->getForeignLink($linkParams, $entityDefs[$foreignEntityType]); + /** @var ?array $foreignParams */ + $foreignParams = $foreignEntityType && $foreignLinkName ? + $this->metadata->get(['entityDefs', $foreignEntityType, 'links', $foreignLinkName]) : + null; - $currentType = $linkParams['type']; + /** @var ?string $relationshipName */ + $relationshipName = $params['relationName'] ?? null; - $relType = $currentType; - - if ($foreignLink !== false) { - $relType .= '_' . $foreignLink['params']['type']; + if ($relationshipName) { + $relationshipName = lcfirst($relationshipName); + $params['relationName'] = $relationshipName; } - $relType = Util::toCamelCase($relType); + $linkType = $params['type']; + $foreignLinkType = $foreignParams ? $foreignParams['type'] : null; - $relationName = $this->relationExists($relType) ? - $relType /*hasManyHasMany*/ : - $currentType /*hasMany*/; + $params['hasField'] = (bool) $this->metadata + ->get(['entityDefs', $entityType, 'fields', $name]); - if (isset($linkParams['relationName'])) { - $className = $this->getRelationClass($linkParams['relationName']); + $relationDefs = RelationDefs::fromRaw($params, $name); - if (!$className) { - $relationName = $this->relationExists($relType) ? $relType : $currentType; - $className = $this->getRelationClass($relationName); + $converter = $this->createLinkConverter($relationshipName, $linkType, $foreignLinkType); + + $convertedEntityDefs = $converter->convert($relationDefs, $entityType); + + $raw = $convertedEntityDefs->toAssoc(); + + if (isset($raw['relations'][$name])) { + $this->mergeAllowedParams($raw['relations'][$name], $params, $foreignParams ?? []); + $this->correct($raw['relations'][$name]); + } + + return [$entityType => $raw]; + } + + private function createLinkConverter(?string $relationship, string $type, ?string $foreignType): LinkConverter + { + $className = $this->getLinkConverterClassName($relationship, $type, $foreignType); + + return $this->injectableFactory->create($className); + } + + /** + * @return class-string + */ + private function getLinkConverterClassName(?string $relationship, string $type, ?string $foreignType): string + { + if ($relationship) { + /** @var class-string $className */ + $className = $this->metadata->get(['app', 'relationships', $relationship, 'converterClassName']); + + if ($className) { + return $className; } - } else { - $className = $this->getRelationClass($relationName); } - if ($className) { - $foreignLinkName = (is_array($foreignLink) && array_key_exists('name', $foreignLink)) ? - $foreignLink['name'] : null; + if ($type === RelationType::HAS_MANY && $foreignType === RelationType::HAS_MANY) { + return ManyMany::class; + } - $helperClass = new $className($this->metadata, $ormMetadata, $entityDefs, $this->config); + if ($type === RelationType::HAS_MANY) { + return HasMany::class; + } - return $helperClass->process($linkName, $entityType, $foreignLinkName, $foreignEntityType); + if ($type === RelationType::HAS_CHILDREN) { + return HasChildren::class; + } + + if ($type === RelationType::HAS_ONE) { + return HasOne::class; + } + + if ($type === RelationType::BELONGS_TO) { + return BelongsTo::class; + } + + if ($type === RelationType::BELONGS_TO_PARENT) { + return BelongsToParent::class; + } + + throw new RuntimeException("Unsupported link type '{$type}'."); + } + + /** + * @param array $relationDefs + * @param array $params + * @param array $foreignParams + */ + private function mergeAllowedParams(array &$relationDefs, array $params, array $foreignParams): void + { + foreach ($this->allowedParams as $name) { + $additionalParam = $this->getAllowedParam($name, $params, $foreignParams); + + if ($additionalParam === null) { + continue; + } + + $relationDefs[$name] = $additionalParam; + } + } + + /** + * @param array $params + * @param array $foreignParams + * @return array|scalar|null + */ + private function getAllowedParam(string $name, array $params, array $foreignParams): mixed + { + $value = $params[$name] ?? null; + $foreignValue = $foreignParams[$name] ?? null; + + if ($value !== null && $foreignValue !== null) { + if (!empty($value) && !is_array($value)) { + return $value; + } + + if (!empty($foreignValue) && !is_array($foreignValue)) { + return $foreignValue; + } + + /** @var array $value */ + /** @var array $foreignValue */ + + /** @var array */ + return Util::merge($value, $foreignValue); + } + + if (isset($value)) { + return $value; + } + + if (isset($foreignValue)) { + return $foreignValue; } return null; } + + /** + * @param array $relationDefs + */ + private function correct(array &$relationDefs): void + { + if (!isset($relationDefs['additionalColumns'])) { + return; + } + + /** @var array> $additionalColumns */ + $additionalColumns = &$relationDefs['additionalColumns']; + + foreach ($additionalColumns as &$columnDefs) { + $columnDefs['type'] ??= AttributeType::VARCHAR; + + if ( + $columnDefs['type'] === AttributeType::VARCHAR && + !isset($columnDefs['len']) + ) { + $columnDefs['len'] = self::DEFAULT_VARCHAR_LENGTH; + } + } + + $relationDefs['additionalColumns'] = $additionalColumns; + } } diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/Base.php b/application/Espo/Core/Utils/Database/Orm/Relations/Base.php deleted file mode 100644 index 32789657e3..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/Base.php +++ /dev/null @@ -1,285 +0,0 @@ - - */ - private $params; - - /** - * @var array - */ - private $foreignParams; - - /** - * @var ?string - */ - protected $foreignLinkName = null; - - /** - * @var ?string - */ - protected $foreignEntityName = null; - - /** - * @var string[] - */ - protected $allowedParams = [ - 'relationName', - 'conditions', - 'additionalColumns', - 'midKeys', - 'noJoin', - 'indexes', - ]; - - /** - * @return array - */ - protected function getParams() - { - return $this->params; - } - - /** - * @return array - */ - protected function getForeignParams() - { - return $this->foreignParams; - } - - /** - * @param array $params - * @return void - */ - protected function setParams(array $params) - { - $this->params = $params; - } - - /** - * @param array $foreignParams - * @return void - */ - protected function setForeignParams(array $foreignParams) - { - $this->foreignParams = $foreignParams; - } - - /** - * @param string $foreignLinkName - * @return void - */ - protected function setForeignLinkName($foreignLinkName) - { - $this->foreignLinkName = $foreignLinkName; - } - - /** - * @return ?string - */ - protected function getForeignLinkName() - { - return $this->foreignLinkName; - } - - /** - * @param string $foreignEntityName - * @return void - */ - protected function setForeignEntityName($foreignEntityName) - { - $this->foreignEntityName = $foreignEntityName; - } - - /** - * @return ?string - */ - protected function getForeignEntityName() - { - return $this->foreignEntityName; - } - - /** - * @return array - */ - protected function getForeignLinkParams() - { - $foreignLinkName = $this->getForeignLinkName(); - $foreignEntityName = $this->getForeignEntityName(); - $foreignLinkParams = $this->getLinkParams($foreignLinkName, $foreignEntityName); - - return $foreignLinkParams; - } - - /** - * - * @param string $linkName - * @param string $entityType - * @param ?string $foreignLinkName - * @param ?string $foreignEntityName - * @return array - */ - public function process($linkName, $entityType, $foreignLinkName, $foreignEntityName) - { - $inputs = [ - 'itemName' => $linkName, - 'entityName' => $entityType, - 'foreignLinkName' => $foreignLinkName, - 'foreignEntityName' => $foreignEntityName, - ]; - - $this->setMethods($inputs); - - $convertedDefs = $this->load($linkName, $entityType); - - $convertedDefs = $this->mergeAllowedParams($convertedDefs); - - if (isset($convertedDefs[$entityType]['relations'][$linkName])) { - $this->correct($convertedDefs[$entityType]['relations'][$linkName]); - } - - $inputs = $this->setArrayValue(null, $inputs); - - $this->setMethods($inputs); - - return $convertedDefs; - } - - /** - * @param array $relationDefs - */ - private function correct(array &$relationDefs): void - { - if (!isset($relationDefs['additionalColumns'])) { - return; - } - - /** @var array> $additionalColumns */ - $additionalColumns = &$relationDefs['additionalColumns']; - - foreach ($additionalColumns as &$columnDefs) { - $columnDefs['type'] ??= Entity::VARCHAR; - - if ( - $columnDefs['type'] === Entity::VARCHAR && - !isset($columnDefs['len']) - ) { - $columnDefs['len'] = self::DEFAULT_VARCHAR_LENGTH; - } - } - - $relationDefs['additionalColumns'] = $additionalColumns; - } - - /** - * @param array $loads - * @return array - */ - private function mergeAllowedParams($loads) - { - $linkName = $this->getLinkName(); - $entityName = $this->getEntityName(); - - if (!empty($this->allowedParams)) { - $linkParams = &$loads[$entityName]['relations'][$linkName]; - - foreach ($this->allowedParams as $name) { - $additionalParam = $this->getAllowedAdditionalParam($name); - - if (isset($additionalParam)) { - $linkParams[$name] = $additionalParam; - - if (is_array($linkParams[$name])) { - $linkParams[$name] = Util::merge($linkParams[$name], $additionalParam); - } - } - } - } - - return $loads; - } - - /** - * @param string $allowedItemName - * @return ?array - */ - private function getAllowedAdditionalParam($allowedItemName) - { - $linkParams = $this->getLinkParams(); - $foreignLinkParams = $this->getForeignLinkParams(); - - $itemLinkParams = $linkParams[$allowedItemName] ?? null; - $itemForeignLinkParams = $foreignLinkParams[$allowedItemName] ?? null; - - $additionalParam = null; - - if (isset($itemLinkParams) && isset($itemForeignLinkParams)) { - if (!empty($itemLinkParams) && !is_array($itemLinkParams)) { - $additionalParam = $itemLinkParams; - } - else if (!empty($itemForeignLinkParams) && !is_array($itemForeignLinkParams)) { - $additionalParam = $itemForeignLinkParams; - } - else { - /** @var array $itemLinkParams */ - /** @var array $itemForeignLinkParams */ - $additionalParam = Util::merge($itemLinkParams, $itemForeignLinkParams); - } - } - else if (isset($itemLinkParams)) { - $additionalParam = $itemLinkParams; - } - else if (isset($itemForeignLinkParams)) { - $additionalParam = $itemForeignLinkParams; - } - - return $additionalParam; - } - - /** - * @param string $linkName - * @param string $entityType - * @return array - */ - protected function load($linkName, $entityType) - { - return []; - } -} diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/BelongsTo.php b/application/Espo/Core/Utils/Database/Orm/Relations/BelongsTo.php deleted file mode 100644 index b6fb79ba56..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/BelongsTo.php +++ /dev/null @@ -1,113 +0,0 @@ - - */ - protected function load($linkName, $entityName) - { - $linkParams = $this->getLinkParams(); - - $foreignEntityName = $this->getForeignEntityName(); - $foreignLinkName = $this->getForeignLinkName(); - - if ($foreignEntityName === null) { - throw new RuntimeException("No foreign-entity-type."); - } - - $index = true; - - if (!empty($linkParams['noIndex'])) { - $index = false; - } - - $noForeignName = false; - $foreign = null; - - if (!empty($linkParams['noForeignName'])) { - $noForeignName = true; - } else { - if (!empty($linkParams['foreignName'])) { - $foreign = $linkParams['foreignName']; - } else { - $foreign = $this->getForeignField('name', $foreignEntityName); - } - } - - if (!empty($linkParams['noJoin'])) { - $fieldNameDefs = [ - 'type' => 'varchar', - 'notStorable' => true, - 'relation' => $linkName, - 'foreign' => $this->getForeignField('name', $foreignEntityName), - ]; - } else { - $fieldNameDefs = [ - 'type' => 'foreign', - 'relation' => $linkName, - 'foreign' => $foreign, - 'notStorable' => false - ]; - } - - $data = [ - $entityName => [ - 'fields' => [ - $linkName.'Id' => [ - 'type' => 'foreignId', - 'index' => $index - ] - ], - 'relations' => [ - $linkName => [ - 'type' => 'belongsTo', - 'entity' => $foreignEntityName, - 'key' => $linkName.'Id', - 'foreignKey' => 'id', - 'foreign' => $foreignLinkName - ] - ] - ] - ]; - - if (!$noForeignName) { - $data[$entityName]['fields'][$linkName.'Name'] = $fieldNameDefs; - } - - return $data; - } -} diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/BelongsToParent.php b/application/Espo/Core/Utils/Database/Orm/Relations/BelongsToParent.php deleted file mode 100644 index b2b775baab..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/BelongsToParent.php +++ /dev/null @@ -1,71 +0,0 @@ - - */ - protected function load($linkName, $entityName) - { - $linkParams = $this->getLinkParams(); - - return [ - $entityName => [ - 'fields' => [ - $linkName.'Id' => [ - 'type' => 'foreignId', - 'index' => $linkName, - ], - $linkName.'Type' => [ - 'type' => 'foreignType', - 'notNull' => false, - 'index' => $linkName, - 'len' => 100, - ], - $linkName.'Name' => [ - 'type' => 'varchar', - 'notStorable' => true, - ], - ], - 'relations' => [ - $linkName => [ - 'type' => 'belongsToParent', - 'key' => $linkName .'Id', - 'foreign' => $linkParams['foreign'] ?? null, - ], - ], - ], - ]; - } -} diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/EntityTeam.php b/application/Espo/Core/Utils/Database/Orm/Relations/EntityTeam.php deleted file mode 100644 index f91f124cda..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/EntityTeam.php +++ /dev/null @@ -1,69 +0,0 @@ - - */ - protected function load($linkName, $entityType) - { - $linkParams = $this->getLinkParams(); - $foreignEntityName = $this->getForeignEntityName(); - - return [ - $entityType => [ - 'relations' => [ - $linkName => [ - 'type' => 'manyMany', - 'entity' => $foreignEntityName, - 'relationName' => lcfirst($linkParams['relationName']), - 'midKeys' => [ - 'entityId', - 'teamId', - ], - 'conditions' => [ - 'entityType' => $entityType, - ], - 'additionalColumns' => [ - 'entityType' => [ - 'type' => 'varchar', - 'len' => 100, - ], - ], - ], - ], - ] - ]; - } -} diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/EntityUser.php b/application/Espo/Core/Utils/Database/Orm/Relations/EntityUser.php deleted file mode 100644 index d0e73227e1..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/EntityUser.php +++ /dev/null @@ -1,69 +0,0 @@ - - */ - protected function load($linkName, $entityType) - { - $linkParams = $this->getLinkParams(); - $foreignEntityName = $this->getForeignEntityName(); - - return [ - $entityType => [ - 'relations' => [ - $linkName => [ - 'type' => 'manyMany', - 'entity' => $foreignEntityName, - 'relationName' => lcfirst($linkParams['relationName']), - 'midKeys' => [ - 'entityId', - 'userId', - ], - 'conditions' => [ - 'entityType' => $entityType, - ], - 'additionalColumns' => [ - 'entityType' => [ - 'type' => 'varchar', - 'len' => 100 - ], - ], - ] - ] - ] - ]; - } -} diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/HasChildren.php b/application/Espo/Core/Utils/Database/Orm/Relations/HasChildren.php deleted file mode 100644 index 36e114026d..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/HasChildren.php +++ /dev/null @@ -1,72 +0,0 @@ - - */ - protected function load($linkName, $entityName) - { - $foreignLinkName = $this->getForeignLinkName(); - $foreignEntityName = $this->getForeignEntityName(); - - $isStub = !$this->getMetadata()->get(['entityDefs', $entityName, 'fields', $linkName]); - - return [ - $entityName => [ - 'fields' => [ - $linkName.'Ids' => [ - 'type' => 'jsonArray', - 'notStorable' => true, - 'isLinkStub' => $isStub, - ], - $linkName.'Names' => [ - 'type' => 'jsonObject', - 'notStorable' => true, - 'isLinkStub' => $isStub, - ], - ], - 'relations' => [ - $linkName => [ - 'type' => 'hasChildren', - 'entity' => $foreignEntityName, - 'foreignKey' => $foreignLinkName.'Id', - 'foreignType' => $foreignLinkName.'Type', - 'foreign' => $foreignLinkName - ], - ], - ], - ]; - } -} diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/HasMany.php b/application/Espo/Core/Utils/Database/Orm/Relations/HasMany.php deleted file mode 100644 index cb21ea98aa..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/HasMany.php +++ /dev/null @@ -1,76 +0,0 @@ - - */ - protected function load($linkName, $entityName) - { - $linkParams = $this->getLinkParams(); - $foreignLinkName = $this->getForeignLinkName(); - $foreignEntityName = $this->getForeignEntityName(); - - $relationType = isset($linkParams['relationName']) ? 'manyMany' : 'hasMany'; - - $isStub = !$this->getMetadata()->get(['entityDefs', $entityName, 'fields', $linkName]); - - $relation = [ - $entityName => [ - 'fields' => [ - $linkName.'Ids' => [ - 'type' => 'jsonArray', - 'notStorable' => true, - 'isLinkStub' => $isStub, - ], - $linkName.'Names' => [ - 'type' => 'jsonObject', - 'notStorable' => true, - 'isLinkStub' => $isStub, - ], - ], - 'relations' => [ - $linkName => [ - 'type' => $relationType, - 'entity' => $foreignEntityName, - 'foreignKey' => lcfirst($foreignLinkName.'Id'), - 'foreign' => $foreignLinkName - ], - ], - ], - ]; - - return $relation; - } -} diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/HasOne.php b/application/Espo/Core/Utils/Database/Orm/Relations/HasOne.php deleted file mode 100644 index 7cf69e3e2a..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/HasOne.php +++ /dev/null @@ -1,101 +0,0 @@ - - */ - protected function load($linkName, $entityType) - { - $linkParams = $this->getLinkParams(); - $foreignLinkName = $this->getForeignLinkName(); - $foreignEntityType = $this->getForeignEntityName(); - - if ($foreignEntityType === null) { - throw new RuntimeException(); - } - - $noForeignName = false; - - $foreign = null; - - if (!empty($linkParams['noForeignName'])) { - $noForeignName = true; - } - else { - if (!empty($linkParams['foreignName'])) { - $foreign = $linkParams['foreignName']; - } - else { - $foreign = $this->getForeignField('name', $foreignEntityType); - } - } - - $relation = [ - $entityType => [ - 'fields' => [ - $linkName.'Id' => [ - 'type' => 'foreign', - 'notStorable' => true, - 'relation' => $linkName, - 'foreign' => 'id', - ], - $linkName.'Name' => [ - 'type' => 'foreign', - 'notStorable' => true, - 'relation' => $linkName, - 'foreign' => $foreign, - ], - ], - 'relations' => [ - $linkName => [ - 'type' => 'hasOne', - 'entity' => $foreignEntityType, - 'foreignKey' => lcfirst($foreignLinkName.'Id'), - 'foreign' => $foreignLinkName, - ], - ] - ] - ]; - - if (!empty($linkParams['noJoin'])) { - $relation[$entityType]['fields'][$linkName.'Name']['type'] = 'varchar'; - $relation[$entityType]['fields'][$linkName.'Id']['type'] = 'varchar'; - } - - return $relation; - } -} diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/ManyMany.php b/application/Espo/Core/Utils/Database/Orm/Relations/ManyMany.php deleted file mode 100644 index 83abb6f65f..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/ManyMany.php +++ /dev/null @@ -1,149 +0,0 @@ - - */ - protected function load($linkName, $entityType) - { - $foreignEntityName = $this->getForeignEntityName(); - $foreignLinkName = $this->getForeignLinkName(); - - if ($foreignEntityName === null) { - throw new RuntimeException("No foreign-entity-type."); - } - - if ($foreignLinkName === null) { - throw new RuntimeException("No foreign-link-name."); - } - - $linkParams = $this->getLinkParams(); - - if (!empty($linkParams['relationName'])) { - $relationName = $linkParams['relationName']; - } else { - $relationName = $this->getJoinTable($entityType, $foreignEntityName); - } - - $isStub = !$this->getMetadata()->get(['entityDefs', $entityType, 'fields', $linkName]); - - $key1 = lcfirst($entityType) . 'Id'; - $key2 = lcfirst($foreignEntityName) . 'Id'; - - if ($key1 === $key2) { - if (strcmp($linkName, $foreignLinkName)) { - $key1 = 'leftId'; - $key2 = 'rightId'; - } else { - $key1 = 'rightId'; - $key2 = 'leftId'; - } - } - - $relationDefs = [ - 'type' => 'manyMany', - 'entity' => $foreignEntityName, - 'relationName' => $relationName, - 'key' => 'id', - 'foreignKey' => 'id', - 'midKeys' => [ - $key1, - $key2, - ], - 'foreign' => $foreignLinkName, - ]; - - $columnAttributeMap = $this->getMetadata() - ->get(['entityDefs', $entityType, 'links', $linkName, 'columnAttributeMap']); - - if ($columnAttributeMap) { - $relationDefs['columnAttributeMap'] = $columnAttributeMap; - } - - return [ - $entityType => [ - 'fields' => [ - $linkName.'Ids' => [ - 'type' => 'jsonArray', - 'notStorable' => true, - 'isLinkStub' => $isStub, - ], - $linkName.'Names' => [ - 'type' => 'jsonObject', - 'notStorable' => true, - 'isLinkStub' => $isStub, - ], - ], - 'relations' => [ - $linkName => $relationDefs, - ], - ], - ]; - } - - /** - * @param string $tableName1 - * @param string $tableName2 - * @return string - */ - protected function getJoinTable($tableName1, $tableName2) - { - $tables = $this->getSortEntities($tableName1, $tableName2); - - return Util::toCamelCase(implode('_', $tables)); - } - - /** - * @param string $entity1 - * @param string $entity2 - * @return array{string, string} - */ - protected function getSortEntities($entity1, $entity2) - { - $entities = [ - Util::toCamelCase(lcfirst($entity1)), - Util::toCamelCase(lcfirst($entity2)), - ]; - - sort($entities); - - return $entities; - } - -} diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/SmsPhoneNumber.php b/application/Espo/Core/Utils/Database/Orm/Relations/SmsPhoneNumber.php deleted file mode 100644 index d72b225726..0000000000 --- a/application/Espo/Core/Utils/Database/Orm/Relations/SmsPhoneNumber.php +++ /dev/null @@ -1,69 +0,0 @@ - - */ - protected function load($linkName, $entityName) - { - $parentRelation = parent::load($linkName, $entityName); - - $foreignEntityName = $this->getForeignEntityName(); - - if ($foreignEntityName === null) { - throw new RuntimeException(); - } - - $relation = [ - $entityName => [ - 'relations' => [ - $linkName => [ - 'midKeys' => [ - lcfirst($entityName) . 'Id', - lcfirst($foreignEntityName) . 'Id', - ], - ], - ], - ], - ]; - - /** @var array */ - return Util::merge($parentRelation, $relation); - } -} diff --git a/application/Espo/Resources/metadata/app/metadata.json b/application/Espo/Resources/metadata/app/metadata.json index 08388e226b..5d84dd9caa 100644 --- a/application/Espo/Resources/metadata/app/metadata.json +++ b/application/Espo/Resources/metadata/app/metadata.json @@ -27,6 +27,7 @@ ["app", "rebuild"], ["app", "smsProviders", "__ANY__", "senderClassName"], ["app", "orm"], + ["app", "relationships"], ["app", "linkManager"], ["app", "hook"], ["app", "api"], diff --git a/application/Espo/Resources/metadata/app/relationships.json b/application/Espo/Resources/metadata/app/relationships.json new file mode 100644 index 0000000000..af3950aca5 --- /dev/null +++ b/application/Espo/Resources/metadata/app/relationships.json @@ -0,0 +1,17 @@ +{ + "attachments": { + "converterClassName": "Espo\\Core\\Utils\\Database\\Orm\\LinkConverters\\Attachments" + }, + "emailEmailAddress": { + "converterClassName": "Espo\\Core\\Utils\\Database\\Orm\\LinkConverters\\EmailEmailAddress" + }, + "entityTeam": { + "converterClassName": "Espo\\Core\\Utils\\Database\\Orm\\LinkConverters\\EntityTeam" + }, + "entityUser": { + "converterClassName": "Espo\\Core\\Utils\\Database\\Orm\\LinkConverters\\EntityUser" + }, + "smsPhoneNumber": { + "converterClassName": "Espo\\Core\\Utils\\Database\\Orm\\LinkConverters\\SmsPhoneNumber" + } +} diff --git a/application/Espo/Resources/metadata/entityDefs/AuthLogRecord.json b/application/Espo/Resources/metadata/entityDefs/AuthLogRecord.json index 6f95602f5e..e359817f09 100644 --- a/application/Espo/Resources/metadata/entityDefs/AuthLogRecord.json +++ b/application/Espo/Resources/metadata/entityDefs/AuthLogRecord.json @@ -82,7 +82,6 @@ "authToken": { "type": "belongsTo", "entity": "AuthToken", - "foreign": "authLog", "foreignName": "id" }, "actionHistoryRecords": { diff --git a/application/Espo/Tools/Export/Export.php b/application/Espo/Tools/Export/Export.php index 9f16bc63f4..05f852f7ad 100644 --- a/application/Espo/Tools/Export/Export.php +++ b/application/Espo/Tools/Export/Export.php @@ -266,6 +266,7 @@ class Export return false; } + // Revise. if ($this->getAttributeParam($entity, $attribute, 'isLinkStub')) { return false; }