From 81ff01e9be320bff4390f63e5e684e2a78a32299 Mon Sep 17 00:00:00 2001 From: yuri Date: Mon, 13 Aug 2018 15:43:44 +0300 Subject: [PATCH] array value --- .../Espo/Core/ORM/Repositories/RDB.php | 13 +++ application/Espo/Core/SelectManagers/Base.php | 54 ++++++++++- .../Core/Utils/Database/Orm/Converter.php | 1 + .../Utils/Database/Orm/Fields/LinkParent.php | 3 +- application/Espo/Entities/ArrayValue.php | 37 ++++++++ application/Espo/Repositories/ArrayValue.php | 95 +++++++++++++++++++ .../metadata/entityDefs/ArrayValue.json | 24 +++++ .../Espo/Resources/metadata/fields/array.json | 3 +- .../Resources/metadata/fields/multiEnum.json | 3 +- .../Resources/metadata/scopes/ArrayValue.json | 7 ++ application/Espo/Services/App.php | 55 +++++++++++ client/src/views/fields/array.js | 86 +++++++++++------ 12 files changed, 346 insertions(+), 35 deletions(-) create mode 100644 application/Espo/Entities/ArrayValue.php create mode 100644 application/Espo/Repositories/ArrayValue.php create mode 100644 application/Espo/Resources/metadata/entityDefs/ArrayValue.json create mode 100644 application/Espo/Resources/metadata/scopes/ArrayValue.json diff --git a/application/Espo/Core/ORM/Repositories/RDB.php b/application/Espo/Core/ORM/Repositories/RDB.php index ae6b912a5d..02bdc2e300 100644 --- a/application/Espo/Core/ORM/Repositories/RDB.php +++ b/application/Espo/Core/ORM/Repositories/RDB.php @@ -287,6 +287,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable $this->processPhoneNumberSave($entity); $this->processSpecifiedRelationsSave($entity); $this->processFileFieldsSave($entity); + $this->processArrayFieldsSave($entity); } if (!$this->hooksDisabled && empty($options['skipHooks'])) { @@ -405,6 +406,18 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable } } + protected function processArrayFieldsSave(Entity $entity) + { + foreach ($entity->getAttributes() as $attribute => $defs) { + if (!isset($defs['type']) || $defs['type'] !== Entity::JSON_ARRAY) continue; + if (!$entity->has($attribute)) continue; + if (!$entity->isAttributeChanged($attribute)) continue; + if (!$entity->getAttributeParam($attribute, 'storeArrayValues')) continue; + if ($entity->getAttributeParam($attribute, 'notStorable')) continue; + $this->getEntityManager()->getRepository('ArrayValue')->storeEntityAttribute($entity, $attribute); + } + } + protected function processEmailAddressSave(Entity $entity) { if ($entity->hasRelation('emailAddresses') && $entity->hasAttribute('emailAddress')) { diff --git a/application/Espo/Core/SelectManagers/Base.php b/application/Espo/Core/SelectManagers/Base.php index 9f3300efc6..fb3032b36f 100644 --- a/application/Espo/Core/SelectManagers/Base.php +++ b/application/Espo/Core/SelectManagers/Base.php @@ -1353,12 +1353,64 @@ class Base $part[$key . '!='] = $value; } } else if ($relationType == 'hasOne') { - $this->addLeftJoin([$link, alias], $result); + $this->addLeftJoin([$link, $alias], $result); $part[$alias . '.id!='] = $value; } else { break; } $this->setDistinct(true, $result); + break; + + case 'arrayAnyOf': + case 'arrayNoneOf': + case 'arrayIsEmpty': + case 'arrayIsNotEmpty': + $arrayValueAlias = 'arrayFilter' . strval(rand(10000, 99999)); + $arrayAttribute = $attribute; + $arrayEntityType = $this->getEntityType(); + $alias = lcfirst($arrayEntityType); + + if (strpos($attribute, '.') > 0) { + list($arrayAttributeLink, $arrayAttribute) = explode('.', $attribute); + $seed = $this->getSeed(); + $arrayEntityType = $seed->getRelationParam($arrayAttributeLink, 'entity'); + $alias = $arrayAttributeLink; + } + + if ($type === 'arrayAnyOf') { + if (is_null($value) || !$value && !is_array($value)) break; + $this->addLeftJoin(['ArrayValue', $arrayValueAlias, [ + $arrayValueAlias . '.entityId:' => $alias . '.id', + $arrayValueAlias . '.entityType' => $arrayEntityType, + $arrayValueAlias . '.attribute' => $arrayAttribute + ]], $result); + $part[$arrayValueAlias . '.value'] = $value; + } else if ($type === 'arrayNoneOf') { + if (is_null($value) || !$value && !is_array($value)) break; + $this->addLeftJoin(['ArrayValue', $arrayValueAlias, [ + $arrayValueAlias . '.entityId:' => $alias . '.id', + $arrayValueAlias . '.entityType' => $arrayEntityType, + $arrayValueAlias . '.attribute' => $arrayAttribute, + $arrayValueAlias . '.value=' => $value, + ]], $result); + $part[$arrayValueAlias . '.id'] = null; + } else if ($type === 'arrayIsEmpty') { + $this->addLeftJoin(['ArrayValue', $arrayValueAlias, [ + $arrayValueAlias . '.entityId:' => $alias . '.id', + $arrayValueAlias . '.entityType' => $arrayEntityType, + $arrayValueAlias . '.attribute' => $arrayAttribute, + ]], $result); + $part[$arrayValueAlias . '.id'] = null; + } else if ($type === 'arrayIsNotEmpty') { + $this->addLeftJoin(['ArrayValue', $arrayValueAlias, [ + $arrayValueAlias . '.entityId:' => $alias . '.id', + $arrayValueAlias . '.entityType' => $arrayEntityType, + $arrayValueAlias . '.attribute' => $arrayAttribute, + ]], $result); + $part[$arrayValueAlias . '.id!='] = null; + } + + $this->setDistinct(true, $result); } } diff --git a/application/Espo/Core/Utils/Database/Orm/Converter.php b/application/Espo/Core/Utils/Database/Orm/Converter.php index cb0a758f9f..2cfdbaf650 100644 --- a/application/Espo/Core/Utils/Database/Orm/Converter.php +++ b/application/Espo/Core/Utils/Database/Orm/Converter.php @@ -90,6 +90,7 @@ class Converter 'select' => 'select', 'orderBy' => 'orderBy', 'where' => 'where', + 'storeArrayValues' => 'storeArrayValues' ); protected $idParams = array( diff --git a/application/Espo/Core/Utils/Database/Orm/Fields/LinkParent.php b/application/Espo/Core/Utils/Database/Orm/Fields/LinkParent.php index 1c88637d98..0c81682e0c 100644 --- a/application/Espo/Core/Utils/Database/Orm/Fields/LinkParent.php +++ b/application/Espo/Core/Utils/Database/Orm/Fields/LinkParent.php @@ -43,7 +43,8 @@ class LinkParent extends Base $fieldName.'Type' => array( 'type' => 'foreignType', 'notNull' => false, - 'index' => $fieldName + 'index' => $fieldName, + 'len' => 100 ), $fieldName.'Name' => array( 'type' => 'varchar', diff --git a/application/Espo/Entities/ArrayValue.php b/application/Espo/Entities/ArrayValue.php new file mode 100644 index 0000000000..8a22cdce00 --- /dev/null +++ b/application/Espo/Entities/ArrayValue.php @@ -0,0 +1,37 @@ +getAttributeType($attribute) === Entity::JSON_ARRAY) { + throw new Error("ArrayValue: Can't store non array attribute."); + } + if ($entity->getAttributeType('notStorable')) return; + if (!$entity->getAttributeParam($attribute, 'storeArrayValues')) return; + if (!$entity->has($attribute)) return; + + $valueList = $entity->get($attribute); + + if (is_null($valueList)) { + $valueList = []; + } + + if (!is_array($valueList)) throw new Error("ArrayValue: Bad value passed to JSON_ARRAY attribute {$attribute}."); + + $valueList = array_unique($valueList); + + $toSkipValueList = []; + + if (!$entity->isNew() && !$populateMode) { + $existingList = $this->where([ + 'entityType' => $entity->getEntityType(), + 'entityId' => $entity->id, + 'attribute' => $attribute + ])->find(); + + foreach ($existingList as $existing) { + if (!in_array($existing->get('value'), $valueList)) { + $this->deleteFromDb($existing->id); + } else { + $toSkipValueList[] = $existing->get('value'); + } + } + } + + foreach ($valueList as $value) { + if (in_array($value, $toSkipValueList)) continue; + if (!is_string($value)) continue; + + $arrayValue = $this->get(); + $arrayValue->set([ + 'entityType' => $entity->getEntityType(), + 'entityId' => $entity->id, + 'attribute' => $attribute, + 'value' => $value + ]); + $this->save($arrayValue); + } + } +} diff --git a/application/Espo/Resources/metadata/entityDefs/ArrayValue.json b/application/Espo/Resources/metadata/entityDefs/ArrayValue.json new file mode 100644 index 0000000000..75970d1e62 --- /dev/null +++ b/application/Espo/Resources/metadata/entityDefs/ArrayValue.json @@ -0,0 +1,24 @@ +{ + "fields": { + "value": { + "type": "varchar", + "required": true, + "maxLength": 100 + }, + "entity": { + "type": "linkParent" + }, + "attribute": { + "type": "varchar", + "maxLength": 100 + } + }, + "indexes": { + "entityTypeValue": { + "columns": ["entityType", "value"] + }, + "entityValue": { + "columns": ["entityType", "entityId", "value"] + } + } +} diff --git a/application/Espo/Resources/metadata/fields/array.json b/application/Espo/Resources/metadata/fields/array.json index 6cbacea370..2b53c33432 100644 --- a/application/Espo/Resources/metadata/fields/array.json +++ b/application/Espo/Resources/metadata/fields/array.json @@ -33,7 +33,8 @@ "notCreatable": false, "notSortable": true, "fieldDefs":{ - "type":"jsonArray" + "type":"jsonArray", + "storeArrayValues": true }, "translatedOptions": true, "personalData": true diff --git a/application/Espo/Resources/metadata/fields/multiEnum.json b/application/Espo/Resources/metadata/fields/multiEnum.json index 620fb8ef37..ed1377b795 100644 --- a/application/Espo/Resources/metadata/fields/multiEnum.json +++ b/application/Espo/Resources/metadata/fields/multiEnum.json @@ -28,7 +28,8 @@ "notCreatable": false, "notSortable": true, "fieldDefs":{ - "type":"jsonArray" + "type":"jsonArray", + "storeArrayValues": true }, "translatedOptions": true, "personalData": true diff --git a/application/Espo/Resources/metadata/scopes/ArrayValue.json b/application/Espo/Resources/metadata/scopes/ArrayValue.json new file mode 100644 index 0000000000..a5280171da --- /dev/null +++ b/application/Espo/Resources/metadata/scopes/ArrayValue.json @@ -0,0 +1,7 @@ +{ + "entity": true, + "layouts": false, + "tab": false, + "acl": false, + "customizable": false +} diff --git a/application/Espo/Services/App.php b/application/Espo/Services/App.php index f9c4c3ed34..4467296e99 100644 --- a/application/Espo/Services/App.php +++ b/application/Espo/Services/App.php @@ -62,6 +62,11 @@ class App extends \Espo\Core\Services\Base return $this->getInjection('entityManager'); } + protected function getMetadata() + { + return $this->getInjection('metadata'); + } + public function getUserData() { $preferencesData = $this->getPreferences()->getValueMap(); @@ -255,4 +260,54 @@ class App extends \Espo\Core\Services\Base $this->getEntityManager()->saveEntity($number); } } + + public function jobPopulateArrayValues() + { + $scopeList = array_keys($this->getMetadata()->get(['scopes'])); + + $sql = "DELETE FROM array_value"; + $this->getEntityManager()->getPdo()->query($sql); + + foreach ($scopeList as $scope) { + if (!$this->getMetadata()->get(['scopes', $scope, 'entity'])) continue; + if ($this->getMetadata()->get(['scopes', $scope, 'disabled'])) continue; + + $seed = $this->getEntityManager()->getEntity($scope); + if (!$seed) continue; + + $attributeList = []; + + foreach ($seed->getAttributes() as $attribute => $defs) { + if (!isset($defs['type']) || $defs['type'] !== \Espo\ORM\Entity::JSON_ARRAY) continue; + if (!$seed->getAttributeParam($attribute, 'storeArrayValues')) continue; + if ($seed->getAttributeParam($attribute, 'notStorable')) continue; + $attributeList[] = $attribute; + } + $select = ['id']; + $orGroup = []; + foreach ($attributeList as $attribute) { + $select[] = $attribute; + $orGroup[$attribute . '!='] = null; + } + + $sql = $this->getEntityManager()->getQuery()->createSelectQuery($scope, [ + 'select' => $select, + 'whereClause' => [ + 'OR' => $orGroup + ] + ]); + $sth = $this->getEntityManager()->getPdo()->prepare($sql); + $sth->execute(); + + while ($dataRow = $sth->fetch(\PDO::FETCH_ASSOC)) { + $entity = $this->getEntityManager()->getEntityFactory()->create($scope); + $entity->set($dataRow); + $entity->setAsFetched(); + + foreach ($attributeList as $attribute) { + $this->getEntityManager()->getRepository('ArrayValue')->storeEntityAttribute($entity, $attribute, true); + } + } + } + } } diff --git a/client/src/views/fields/array.js b/client/src/views/fields/array.js index 289b8574df..437724fcf4 100644 --- a/client/src/views/fields/array.js +++ b/client/src/views/fields/array.js @@ -40,7 +40,7 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi searchTemplate: 'fields/array/search', - searchTypeList: ['anyOf'], + searchTypeList: ['anyOf', 'noneOf', 'isEmpty', 'isNotEmpty'], maxItemLength: null, @@ -117,10 +117,6 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi if (this.options.customOptionList) { this.setOptionList(this.options.customOptionList, true); } - - if (this.mode === 'search') { - this.searchTypeList = Espo.Utils.clone(this.searchTypeList); - } }, setupSearch: function () { @@ -377,36 +373,64 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi }, fetchSearch: function () { - var field = this.name; + var type = this.$el.find('select.search-type').val() || 'anyOf'; + var arr = []; var arrFront = []; - var list = this.$element.val().split(':,:'); - if (list.length == 1 && list[0] == '') { - list = []; - } - - list.forEach(function(value) { - arr.push({ - type: 'like', - field: field, - value: "%" + value.replace(/\//g, '\\\\/' ) + "%" - }); - arrFront.push(value); - }); - - if (arr.length == 0) { - return false; - } - - var data = { - type: 'or', - value: arr, - data: { - valueList: arrFront + if (~['anyOf', 'noneOf'].indexOf(type)) { + var valueList = this.$element.val().split(':,:'); + if (valueList.length == 1 && valueList[0] == '') { + valueList = []; } - }; - return data; + } + + if (type === 'anyOf') { + var data = { + type: 'arrayAnyOf', + value: valueList, + data: { + type: 'anyOf', + valueList: valueList + } + }; + if (!valueList.length) { + data.value = null; + } + return data; + } + + if (type === 'noneOf') { + var data = { + type: 'arrayNoneOf', + value: valueList, + data: { + type: 'noneOf', + valueList: valueList + } + }; + return data; + } + + if (type === 'isEmpty') { + var data = { + type: 'arrayIsEmpty', + data: { + type: 'isEmpty' + } + }; + return data; + } + + if (type === 'isNotEmpty') { + var data = { + type: 'arrayIsNotEmpty', + data: { + type: 'isNotEmpty' + } + }; + return data; + } }, validateRequired: function () {