N°9934 - OQL with magic field (#1011)

This commit is contained in:
Anne-Catherine
2026-08-19 11:46:28 +02:00
committed by GitHub
parent 31eddad029
commit 1cdc2c5648
9 changed files with 139 additions and 8 deletions

View File

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

View File

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

View File

@@ -386,4 +386,8 @@ class AttributeBlob extends AttributeDefinition
{
return CMDBChangeOpSetAttributeBlob::class;
}
public function IsValidFilterAttribute(): bool
{
return false;
}
}

View File

@@ -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 */

View File

@@ -92,5 +92,8 @@ class AttributeEncryptedString extends AttributeString implements iAttributeNoGr
{
return CMDBChangeOpSetAttributeEncrypted::class;
}
public function IsValidFilterAttribute(): bool
{
return false;
}
}

View File

@@ -248,4 +248,9 @@ class AttributeOneWayPassword extends AttributeDefinition implements iAttributeN
{
return CMDBChangeOpSetAttributeOneWayPassword::class;
}
public function IsValidFilterAttribute(): bool
{
return false;
}
}

View File

@@ -68,6 +68,10 @@ class AttributePassword extends AttributeString implements iAttributeNoGroupBy
return '******';
}
}
public function IsValidFilterAttribute(): bool
{
return false;
}
public function IsPartOfFingerprint()
{

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.7">
<classes>
<class id="OQLResolverParent" _created_in="itop-structure" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<category>bizmodel</category>
<abstract>true</abstract>
<key_type>autoincrement</key_type>
<db_table>oqlresolverparent</db_table>
<db_key_field>id</db_key_field>
<fields_semantic>
<state_attribute>status</state_attribute>
</fields_semantic>
</properties>
<presentation/>
<methods/>
<fields>
<field id="cumulatedpending" xsi:type="AttributeStopWatch">
<states>
<state id="pending"/>
</states>
<working_time/>
<thresholds/>
<goal/>
</field><field id="status" xsi:type="AttributeEnum">
<always_load_in_tables>true</always_load_in_tables>
<sort_type>rank</sort_type>
<values>
<value id="new">
<code>new</code>
</value>
<value id="pending">
<code>pending</code>
</value>
</values>
<sql>status</sql>
<default_value>new</default_value>
<is_null_allowed>false</is_null_allowed>
</field>
</fields>
</class>
<class id="OQLResolverChild" _created_in="itop-structure" _delta="define">
<parent>OQLResolverParent</parent>
<properties>
<category>bizmodel</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>oqlresolverchild</db_table>
<db_key_field>id</db_key_field>
</properties>
<presentation/>
<methods/>
<fields>
<field id="name" xsi:type="AttributeString">
<sql>name</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
</field>
</fields>
</class>
</classes>
</itop_design>

View File

@@ -0,0 +1,41 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
use DBObjectSet;
use DBSearch;
use MetaModel;
class OQLResolverTest extends ItopCustomDatamodelTestCase
{
public function GetDatamodelDeltaAbsPath(): string
{
return __DIR__.'/Delta/delta_oql_resolver.xml';
}
public function testQueryOnMagicalFields()
{
// Given
$sObjectKey = $this->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');
}
}