full name additional attribute list

This commit is contained in:
Yuri Kuznetsov
2025-06-30 11:18:41 +03:00
parent d00fca02c1
commit f00e86cd21
4 changed files with 289 additions and 127 deletions

View File

@@ -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<string, array<string, string[]>> */
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<string, mixed> $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") ?? [];
}
}

View File

@@ -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;

View File

@@ -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",

View File

@@ -0,0 +1,132 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* 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 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<string, mixed> $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);
}
}