diff --git a/application/Espo/Core/Utils/FieldUtil.php b/application/Espo/Core/Utils/FieldUtil.php index d9c027db24..dd41247f80 100644 --- a/application/Espo/Core/Utils/FieldUtil.php +++ b/application/Espo/Core/Utils/FieldUtil.php @@ -33,6 +33,13 @@ use Espo\ORM\Defs\Params\FieldParam; class FieldUtil { + private const PARAM_ACTUAL = 'actual'; + private const PARAM_NOT_ACTUAL = 'notActual'; + private const PARAM_NAMING = 'naming'; + + private const NAMING_PREFIX = 'prefix'; + private const NAMING_SUFFIX = 'suffix'; + /** @var array> */ private $fieldByTypeListCache = []; @@ -59,7 +66,7 @@ class FieldUtil return []; } - $defs = $this->metadata->get('fields.' . $fieldType); + $defs = $this->metadata->get("fields.$fieldType"); if (!$defs) { return []; @@ -69,37 +76,39 @@ class FieldUtil $defs = get_object_vars($defs); } - $fieldList = []; + $output = []; - if (isset($defs[$type . 'Fields'])) { - $list = $defs[$type . 'Fields']; - - $naming = 'suffix'; - - if (isset($defs['naming'])) { - $naming = $defs['naming']; + if (!isset($defs[$type . 'Fields'])) { + if ($type === self::PARAM_ACTUAL) { + $output[] = $name; } - if ($naming == 'prefix') { - foreach ($list as $f) { - if ($f === '') { - $fieldList[] = $name; - } else { - $fieldList[] = $f . ucfirst($name); - } - } - } else { - foreach ($list as $f) { - $fieldList[] = $name . ucfirst($f); + return $output; + } + + $list = $defs[$type . 'Fields']; + + $naming = self::NAMING_SUFFIX; + + if (isset($defs[self::PARAM_NAMING])) { + $naming = $defs[self::PARAM_NAMING]; + } + + if ($naming === self::NAMING_PREFIX) { + foreach ($list as $it) { + if ($it === '') { + $output[] = $name; + } else { + $output[] = $it . ucfirst($name); } } } else { - if ($type == 'actual') { - $fieldList[] = $name; + foreach ($list as $it) { + $output[] = $name . ucfirst($it); } } - return $fieldList; + return $output; } /** @@ -107,11 +116,9 @@ class FieldUtil */ public function getAdditionalActualAttributeList(string $entityType, string $name): array { - $attributeList = []; - $list = $this->metadata->get(['entityDefs', $entityType, 'fields', $name, 'additionalAttributeList']); - if (empty($list)) { + if (!$list) { return []; } @@ -121,19 +128,21 @@ class FieldUtil return []; } - $naming = $this->metadata->get(['fields', $type, 'naming'], 'suffix'); + $naming = $this->metadata->get("fields.$type.naming") ?? self::NAMING_SUFFIX; - if ($naming == 'prefix') { - foreach ($list as $f) { - $attributeList[] = $f . ucfirst($name); + $output = []; + + if ($naming === self::NAMING_PREFIX) { + foreach ($list as $it) { + $output[] = $it . ucfirst($name); } } else { - foreach ($list as $f) { - $attributeList[] = $name . ucfirst($f); + foreach ($list as $it) { + $output[] = $name . ucfirst($it); } } - return $attributeList; + return $output; } /** @@ -144,8 +153,9 @@ class FieldUtil public function getActualAttributeList(string $entityType, string $field): array { return array_merge( - $this->getAttributeListByType($entityType, $field, 'actual'), - $this->getAdditionalActualAttributeList($entityType, $field) + $this->getAttributeListByType($entityType, $field, self::PARAM_ACTUAL), + $this->getAdditionalActualAttributeList($entityType, $field), + $this->getFullNameAdditionalActualAttributeList($entityType, $field), ); } @@ -156,7 +166,7 @@ class FieldUtil */ public function getNotActualAttributeList(string $entityType, string $field): array { - return $this->getAttributeListByType($entityType, $field, 'notActual'); + return $this->getAttributeListByType($entityType, $field, self::PARAM_NOT_ACTUAL); } /** @@ -206,39 +216,42 @@ class FieldUtil */ private function getFieldTypeAttributeListByType(string $fieldType, string $name, string $type): array { + /** @var ?array $defs */ $defs = $this->metadata->get(['fields', $fieldType]); if (!$defs) { return []; } - $attributeList = []; + $output = []; - if (isset($defs[$type . 'Fields'])) { - $list = $defs[$type . 'Fields']; - - $naming = 'suffix'; - - if (isset($defs['naming'])) { - $naming = $defs['naming']; + if (!isset($defs[$type . 'Fields'])) { + if ($type === self::PARAM_ACTUAL) { + $output[] = $name; } - if ($naming == 'prefix') { - foreach ($list as $f) { - $attributeList[] = $f . ucfirst($name); - } - } else { - foreach ($list as $f) { - $attributeList[] = $name . ucfirst($f); - } + return $output; + } + + $list = $defs[$type . 'Fields']; + + $naming = self::NAMING_SUFFIX; + + if (isset($defs['naming'])) { + $naming = $defs['naming']; + } + + if ($naming === self::NAMING_PREFIX) { + foreach ($list as $f) { + $output[] = $f . ucfirst($name); } } else { - if ($type == 'actual') { - $attributeList[] = $name; + foreach ($list as $f) { + $output[] = $name . ucfirst($f); } } - return $attributeList; + return $output; } /** @@ -249,8 +262,8 @@ class FieldUtil public function getFieldTypeAttributeList(string $fieldType, string $name): array { return array_merge( - $this->getFieldTypeAttributeListByType($fieldType, $name, 'actual'), - $this->getFieldTypeAttributeListByType($fieldType, $name, 'notActual') + $this->getFieldTypeAttributeListByType($fieldType, $name, self::PARAM_ACTUAL), + $this->getFieldTypeAttributeListByType($fieldType, $name, self::PARAM_NOT_ACTUAL) ); } @@ -305,4 +318,13 @@ class FieldUtil return null; } + + /** + * @return string[] + */ + private function getFullNameAdditionalActualAttributeList(string $entityType, string $field): array + { + /** @var string[] */ + return $this->metadata->get("entityDefs.$entityType.fields.$field.fullNameAdditionalAttributeList") ?? []; + } } diff --git a/client/src/field-manager.js b/client/src/field-manager.js index b6a1c1d189..88bafd9d91 100644 --- a/client/src/field-manager.js +++ b/client/src/field-manager.js @@ -157,35 +157,31 @@ class FieldManager { * @returns {string[]} */ getActualAttributeList(fieldType, fieldName) { - const fieldNames = []; + const output = []; - if (fieldType in this.defs) { - if ('actualFields' in this.defs[fieldType]) { - const actualFields = this.defs[fieldType].actualFields; - - let naming = 'suffix'; - - if ('naming' in this.defs[fieldType]) { - naming = this.defs[fieldType].naming; - } - - if (naming === 'prefix') { - actualFields.forEach(f => { - fieldNames.push(f + Espo.Utils.upperCaseFirst(fieldName)); - }); - } - else { - actualFields.forEach(f => { - fieldNames.push(fieldName + Espo.Utils.upperCaseFirst(f)); - }); - } - } - else { - fieldNames.push(fieldName); - } + if (!(fieldType in this.defs)) { + return []; } - return fieldNames; + if ('actualFields' in this.defs[fieldType]) { + const actualFields = this.defs[fieldType].actualFields; + + let naming = 'suffix'; + + if ('naming' in this.defs[fieldType]) { + naming = this.defs[fieldType].naming; + } + + if (naming === 'prefix') { + actualFields.forEach(it => output.push(it + Espo.Utils.upperCaseFirst(fieldName))); + } else { + actualFields.forEach(it => output.push(fieldName + Espo.Utils.upperCaseFirst(it))); + } + } else { + output.push(fieldName); + } + + return output; } /** @@ -198,37 +194,37 @@ class FieldManager { * @returns {string[]} */ getNotActualAttributeList(fieldType, fieldName) { - const fieldNames = []; - - if (fieldType in this.defs) { - if ('notActualFields' in this.defs[fieldType]) { - const notActualFields = this.defs[fieldType].notActualFields; - - let naming = 'suffix'; - - if ('naming' in this.defs[fieldType]) { - naming = this.defs[fieldType].naming; - } - - if (naming === 'prefix') { - notActualFields.forEach(f => { - if (f === '') { - fieldNames.push(fieldName); - } - else { - fieldNames.push(f + Espo.Utils.upperCaseFirst(fieldName)); - } - }); - } - else { - notActualFields.forEach(f => { - fieldNames.push(fieldName + Espo.Utils.upperCaseFirst(f)); - }); - } - } + if (!(fieldType in this.defs)) { + return []; } - return fieldNames; + if (!('notActualFields' in this.defs[fieldType])) { + return []; + } + + const notActualFields = this.defs[fieldType].notActualFields; + + let naming = 'suffix'; + + if ('naming' in this.defs[fieldType]) { + naming = this.defs[fieldType].naming; + } + + const output = []; + + if (naming === 'prefix') { + notActualFields.forEach(it => { + if (it === '') { + output.push(fieldName); + } else { + output.push(it + Espo.Utils.upperCaseFirst(fieldName)); + } + }); + } else { + notActualFields.forEach(it => output.push(fieldName + Espo.Utils.upperCaseFirst(it))); + } + + return output; } /** @@ -247,7 +243,8 @@ class FieldManager { return _.union( this.getAttributeList(type, field), - this._getEntityTypeFieldAdditionalAttributeList(entityType, field) + this._getEntityTypeFieldAdditionalAttributeList(entityType, field), + this._getEntityTypeFieldFullNameAdditionalAttributeList(entityType, field), ); } @@ -267,7 +264,8 @@ class FieldManager { return _.union( this.getActualAttributeList(type, field), - this._getEntityTypeFieldAdditionalAttributeList(entityType, field) + this._getEntityTypeFieldAdditionalAttributeList(entityType, field), + this._getEntityTypeFieldFullNameAdditionalAttributeList(entityType, field), ); } @@ -386,14 +384,6 @@ class FieldManager { return list; } - /** - * @deprecated Since v5.7. - * @todo Remove. - */ - getScopeFieldList(entityType) { - return this.getEntityTypeFieldList(entityType); - } - /** * Get a field parameter value. * @@ -424,7 +414,7 @@ class FieldManager { /** * @deprecated Use `getParamList`. - * @todo Remove. + * @todo Remove in v10.0. */ getParams(fieldType) { return this.getParamList(fieldType); @@ -432,7 +422,7 @@ class FieldManager { /** * @deprecated Use `getAttributeList`. - * @todo Remove. + * @todo Remove in v10.0. */ getAttributes(fieldType, fieldName) { return this.getAttributeList(fieldType, fieldName); @@ -440,7 +430,7 @@ class FieldManager { /** * @deprecated Use `getActualAttributeList`. - * @todo Remove. + * @todo Remove in v10.0. */ getActualAttributes(fieldType, fieldName) { return this.getActualAttributeList(fieldType, fieldName); @@ -448,6 +438,7 @@ class FieldManager { /** * @deprecated Use `getNotActualAttributeList`. + * @todo Remove in v10.0. */ getNotActualAttributes(fieldType, fieldName) { return this.getNotActualAttributeList(fieldType, fieldName); @@ -487,11 +478,21 @@ class FieldManager { /** * @deprecated Use `isEntityTypeFieldAvailable`. - * @todo Remove. + * @todo Remove in v10.0. */ isScopeFieldAvailable(entityType, field) { return this.isEntityTypeFieldAvailable(entityType, field); } + + /** + * @param {string} entityType + * @param {string} field + * @return {string[]} + * @private + */ + _getEntityTypeFieldFullNameAdditionalAttributeList(entityType, field) { + return this.metadata.get(`entityDefs.${entityType}.fields.${field}.fullNameAdditionalAttributeList`) ?? []; + } } export default FieldManager; diff --git a/schema/metadata/entityDefs.json b/schema/metadata/entityDefs.json index d6d6392429..8d1a9d828a 100644 --- a/schema/metadata/entityDefs.json +++ b/schema/metadata/entityDefs.json @@ -1082,7 +1082,14 @@ "items": { "type": "string" }, - "description": "Additional attributes for a field. Attribute name will be derived by concatenation with the field name." + "description": "Additional attributes for a field. Attribute names will be derived by concatenating specified names with the field name." + }, + "fullNameAdditionalAttributeList": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional attributes for a field. As of v9.2." }, "dependeeAttributeList": { "type": "array", diff --git a/tests/unit/Espo/Core/Utils/FieldUtilTest.php b/tests/unit/Espo/Core/Utils/FieldUtilTest.php new file mode 100644 index 0000000000..5861c1788b --- /dev/null +++ b/tests/unit/Espo/Core/Utils/FieldUtilTest.php @@ -0,0 +1,132 @@ +. + * + * 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 tests\unit\Espo\Core\Utils; + +use Espo\Core\Utils\FieldUtil; +use Espo\Core\Utils\Metadata; +use Espo\Core\Utils\Util; +use PHPUnit\Framework\TestCase; + +class FieldUtilTest extends TestCase +{ + private ?Metadata $metadata = null; + + protected function setUp(): void + { + $this->metadata = $this->createMock(Metadata::class); + } + + /** + * @param array $data + */ + private function initMetadata(array $data): void + { + $this->metadata + ->expects($this->any()) + ->method('get') + ->willReturnCallback(function ($key, $default) use ($data) { + return Util::getValueByKey($data, $key, $default); + }); + } + + public function testGetActualAttributeListSuffix(): void + { + $this->initMetadata([ + 'fields' => [ + 'testType' => [ + 'naming' => 'suffix', + 'actualFields' => [ + '', + 'helloOne', + ], + ], + ], + 'entityDefs' => [ + 'Test' => [ + 'fields' => [ + 'test' => [ + 'type' => 'testType', + 'additionalAttributeList' => ['helloTwo'], + 'fullNameAdditionalAttributeList' => ['helloThree'], + ] + ] + ] + ] + ]); + + $fieldUtil = new FieldUtil($this->metadata); + + $actual = $fieldUtil->getActualAttributeList('Test', 'test'); + + $this->assertEquals([ + 'test', + 'testHelloOne', + 'testHelloTwo', + 'helloThree', + ], $actual); + } + + public function testGetActualAttributeListPrefix(): void + { + $this->initMetadata([ + 'fields' => [ + 'testType' => [ + 'naming' => 'prefix', + 'actualFields' => [ + '', + 'helloOne', + ], + ], + ], + 'entityDefs' => [ + 'Test' => [ + 'fields' => [ + 'test' => [ + 'type' => 'testType', + 'additionalAttributeList' => ['helloTwo'], + 'fullNameAdditionalAttributeList' => ['helloThree'], + ] + ] + ] + ] + ]); + + $fieldUtil = new FieldUtil($this->metadata); + + $actual = $fieldUtil->getActualAttributeList('Test', 'test'); + + $this->assertEquals([ + 'test', + 'helloOneTest', + 'helloTwoTest', + 'helloThree', + ], $actual); + } +}