From 1cdc2c5648c98ae83d1ab138480575be2cc02abd Mon Sep 17 00:00:00 2001 From: Anne-Catherine <57360138+accognet@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:46:28 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B09934=20-=20OQL=20with=20magic=20field=20?= =?UTF-8?q?(#1011)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/metamodel.class.php | 7 +- core/oqlactualclasstreeresolver.class.inc.php | 6 +- .../AttributeDefinition/AttributeBlob.php | 4 ++ .../AttributeDefinition.php | 10 +++ .../AttributeEncryptedString.php | 5 +- .../AttributeOneWayPassword.php | 5 ++ .../AttributeDefinition/AttributePassword.php | 4 ++ .../core/Delta/delta_oql_resolver.xml | 65 +++++++++++++++++++ .../unitary-tests/core/OQLResolverTest.php | 41 ++++++++++++ 9 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 tests/php-unit-tests/unitary-tests/core/Delta/delta_oql_resolver.xml create mode 100644 tests/php-unit-tests/unitary-tests/core/OQLResolverTest.php diff --git a/core/metamodel.class.php b/core/metamodel.class.php index ddc9dcb322..9e942f9bd4 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1642,10 +1642,10 @@ abstract class MetaModel if ($sFilterCode == 'id') { return true; } - if (array_key_exists($sClass, self::$m_aMagicFields) && array_key_exists($sFilterCode, self::$m_aMagicFields[$sClass])) { + if (array_key_exists($sClass, self::$m_aMagicFields) && in_array($sFilterCode, self::$m_aMagicFields[$sClass])) { return true; } - if (array_key_exists($sClass, self::$m_aFilterForbiddenAttributes) && array_key_exists($sFilterCode, self::$m_aFilterForbiddenAttributes[$sClass])) { + if (array_key_exists($sClass, self::$m_aFilterForbiddenAttributes) && in_array($sFilterCode, self::$m_aFilterForbiddenAttributes[$sClass])) { return false; } return self::IsValidAttCode($sClass, $sFilterCode); @@ -2963,8 +2963,9 @@ abstract class MetaModel self::$m_aMagicFields[] = $sClass; } self::$m_aMagicFields[$sClass][] = $sCode; + self::$m_aAttribOrigins[$sClass][$sCode] = self::$m_aAttribOrigins[$sClass][$sAttCode]; } - if (!$oAttDef->IsSearchable()) { + if (!$oAttDef->IsValidFilterAttribute()) { if (!array_key_exists($sClass, self::$m_aFilterForbiddenAttributes)) { self::$m_aFilterForbiddenAttributes[] = $sClass; } diff --git a/core/oqlactualclasstreeresolver.class.inc.php b/core/oqlactualclasstreeresolver.class.inc.php index 46fdb0470e..7c652127fc 100644 --- a/core/oqlactualclasstreeresolver.class.inc.php +++ b/core/oqlactualclasstreeresolver.class.inc.php @@ -60,15 +60,13 @@ class OQLActualClassTreeResolver $aTranslateFields = []; foreach ($aExpectedAttributes as $sAttCode => $oExpression) { // 'id' is managed later - if ($sAttCode == 'id') { + if ($sAttCode === 'id') { continue; } // Attributes can be stored in attributes list or for magic ones into filter codes list. $sOriginClass = null; - if (MetaModel::IsValidAttCode($sClass, $sAttCode)) { + if (MetaModel::IsValidAttCode($sClass, $sAttCode) || MetaModel::IsValidFilterCode($sClass, $sAttCode)) { $sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode); - } elseif ($sAttCode == 'id') { - $sOriginClass = $sClass; } else { continue; } diff --git a/sources/Core/AttributeDefinition/AttributeBlob.php b/sources/Core/AttributeDefinition/AttributeBlob.php index e8c97d394d..3f3c82c101 100644 --- a/sources/Core/AttributeDefinition/AttributeBlob.php +++ b/sources/Core/AttributeDefinition/AttributeBlob.php @@ -386,4 +386,8 @@ class AttributeBlob extends AttributeDefinition { return CMDBChangeOpSetAttributeBlob::class; } + public function IsValidFilterAttribute(): bool + { + return false; + } } diff --git a/sources/Core/AttributeDefinition/AttributeDefinition.php b/sources/Core/AttributeDefinition/AttributeDefinition.php index c238a72ddd..a2b0739b83 100644 --- a/sources/Core/AttributeDefinition/AttributeDefinition.php +++ b/sources/Core/AttributeDefinition/AttributeDefinition.php @@ -193,6 +193,16 @@ abstract class AttributeDefinition return $this->GetSearchType() != static::SEARCH_WIDGET_TYPE_RAW; } + /** + * Indicates whether this attribute can be used in search filters in OQL queries + * + * @return bool + */ + public function IsValidFilterAttribute(): bool + { + return true; + } + /** @var string */ protected $m_sCode; /** @var array */ diff --git a/sources/Core/AttributeDefinition/AttributeEncryptedString.php b/sources/Core/AttributeDefinition/AttributeEncryptedString.php index 54b2e0d998..f90240bddf 100644 --- a/sources/Core/AttributeDefinition/AttributeEncryptedString.php +++ b/sources/Core/AttributeDefinition/AttributeEncryptedString.php @@ -92,5 +92,8 @@ class AttributeEncryptedString extends AttributeString implements iAttributeNoGr { return CMDBChangeOpSetAttributeEncrypted::class; } - + public function IsValidFilterAttribute(): bool + { + return false; + } } diff --git a/sources/Core/AttributeDefinition/AttributeOneWayPassword.php b/sources/Core/AttributeDefinition/AttributeOneWayPassword.php index a3102c63bb..b6cb21fde7 100644 --- a/sources/Core/AttributeDefinition/AttributeOneWayPassword.php +++ b/sources/Core/AttributeDefinition/AttributeOneWayPassword.php @@ -248,4 +248,9 @@ class AttributeOneWayPassword extends AttributeDefinition implements iAttributeN { return CMDBChangeOpSetAttributeOneWayPassword::class; } + + public function IsValidFilterAttribute(): bool + { + return false; + } } diff --git a/sources/Core/AttributeDefinition/AttributePassword.php b/sources/Core/AttributeDefinition/AttributePassword.php index 42bf2b80e6..608b34eb74 100644 --- a/sources/Core/AttributeDefinition/AttributePassword.php +++ b/sources/Core/AttributeDefinition/AttributePassword.php @@ -68,6 +68,10 @@ class AttributePassword extends AttributeString implements iAttributeNoGroupBy return '******'; } } + public function IsValidFilterAttribute(): bool + { + return false; + } public function IsPartOfFingerprint() { diff --git a/tests/php-unit-tests/unitary-tests/core/Delta/delta_oql_resolver.xml b/tests/php-unit-tests/unitary-tests/core/Delta/delta_oql_resolver.xml new file mode 100644 index 0000000000..494e266e89 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/core/Delta/delta_oql_resolver.xml @@ -0,0 +1,65 @@ + + + + + cmdbAbstractObject + + bizmodel + true + autoincrement + oqlresolverparent + id + + status + + + + + + + + + + + + + + true + rank + + + new + + + pending + + + status + new + false + + + + + + + OQLResolverParent + + bizmodel + false + autoincrement + oqlresolverchild + id + + + + + + name + + false + + + + + \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/core/OQLResolverTest.php b/tests/php-unit-tests/unitary-tests/core/OQLResolverTest.php new file mode 100644 index 0000000000..eda0d7bf64 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/core/OQLResolverTest.php @@ -0,0 +1,41 @@ +GivenObjectInDB('OQLResolverChild', ['name' => 'toto', 'status' => 'new']); + $oObject = MetaModel::GetObject('OQLResolverChild', $sObjectKey); + + // When actions ApplyStimulus then next action fails + $sFilter = "SELECT OQLResolverChild WHERE ISNULL(cumulatedpending_laststart)"; + $oSearch = DBSearch::FromOQL($sFilter); + + $oSet = new DBObjectSet($oSearch); + $oSet->OptimizeColumnLoad(['OQLResolverChild' => ['cumulatedpending']]); + + $oActual = $oSet->Fetch(); + + // Then + // Check status... + $this->assertEquals($oObject->Get('name'), $oActual->Get('name'), 'The query should have returned the object'); + } +}