mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-17 06:18:44 +02:00
Merge branch 'support/2.7' into develop
This commit is contained in:
@@ -2602,9 +2602,18 @@ class DBObjectSearch extends DBSearch
|
||||
return $oExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aAttCodes array of attCodes to search into
|
||||
* @param string $sNeedle one word to be searched
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function AddCondition_FullTextOnAttributes(array $aAttCodes, $sNeedle)
|
||||
{
|
||||
}
|
||||
|
||||
public function ListParameters()
|
||||
{
|
||||
return $this->GetCriteria()->ListParameters();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1919,6 +1919,39 @@ class FieldExpression extends UnaryExpression
|
||||
// Has been resolved into an SQL expression
|
||||
class FieldExpressionResolved extends FieldExpression
|
||||
{
|
||||
protected $m_aAdditionalExpressions;
|
||||
|
||||
public function __construct($mExpression, $sParent = '')
|
||||
{
|
||||
$this->m_aAdditionalExpressions = array();
|
||||
if (is_array($mExpression))
|
||||
{
|
||||
foreach ($mExpression as $sSuffix => $sExpression)
|
||||
{
|
||||
if ($sSuffix == '')
|
||||
{
|
||||
$sName = $sExpression;
|
||||
}
|
||||
$this->m_aAdditionalExpressions[$sSuffix] = new FieldExpressionResolved($sExpression, $sParent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sName = $mExpression;
|
||||
}
|
||||
|
||||
parent::__construct($sName, $sParent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array of additional expressions for muti-column attributes
|
||||
* @since 2.7.4
|
||||
*/
|
||||
public function AdditionalExpressions()
|
||||
{
|
||||
return $this->m_aAdditionalExpressions;
|
||||
}
|
||||
|
||||
public function GetUnresolvedFields($sAlias, &$aUnresolved)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -331,4 +331,12 @@ class OQLJoin
|
||||
return $this->sRightField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetLeftField()
|
||||
{
|
||||
return $this->sLeftField;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,8 +50,15 @@ class OQLClassTreeOptimizer
|
||||
{
|
||||
if ($oJoin->IsOutbound())
|
||||
{
|
||||
// The join is not used, remove from tree
|
||||
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
|
||||
// If joined class in not the same class than the external key target class
|
||||
// then the join cannot be removed because it is used to filter the request
|
||||
$sJoinedClass = $oJoin->GetOOQLClassNode()->GetNodeClass();
|
||||
$sExtKeyAttCode = $oJoin->GetLeftField();
|
||||
$oExtKeyAttDef = MetaModel::GetAttributeDef($oCurrentClassNode->GetNodeClass(), $sExtKeyAttCode);
|
||||
if ($sJoinedClass == $oExtKeyAttDef->GetTargetClass()) {
|
||||
// The join is not used, remove from tree
|
||||
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -178,6 +178,14 @@ class QueryBuilderExpressions
|
||||
foreach ($this->m_aSelectExpr as $sColAlias => $oExpr)
|
||||
{
|
||||
$this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
|
||||
if ($this->m_aSelectExpr[$sColAlias] instanceof FieldExpressionResolved)
|
||||
{
|
||||
// Split the field with the relevant alias
|
||||
foreach ($this->m_aSelectExpr[$sColAlias]->AdditionalExpressions() as $sSuffix => $oAdditionalExpr)
|
||||
{
|
||||
$this->m_aSelectExpr[$sColAlias.$sSuffix] = $oAdditionalExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->m_aGroupByExpr)
|
||||
{
|
||||
|
||||
@@ -239,24 +239,16 @@ class SQLObjectQueryBuilder
|
||||
continue;
|
||||
}
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr)
|
||||
$oFieldSQLExp = new FieldExpressionResolved($oAttDef->GetSQLExpressions(), $sClassAlias);
|
||||
/**
|
||||
* @var string $sPluginClass
|
||||
* @var iQueryModifier $oQueryModifier
|
||||
*/
|
||||
foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
|
||||
{
|
||||
if (!empty($sColId))
|
||||
{
|
||||
// Multi column attributes
|
||||
$oBuild->m_oQBExpressions->AddSelect($sSelectedClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias));
|
||||
}
|
||||
$oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sClassAlias);
|
||||
/**
|
||||
* @var string $sPluginClass
|
||||
* @var iQueryModifier $oQueryModifier
|
||||
*/
|
||||
foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
|
||||
{
|
||||
$oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sClass, $sAttCode, $sColId, $oFieldSQLExp, $oBaseSQLQuery);
|
||||
}
|
||||
$aTranslation[$sClassAlias][$sAttCode.$sColId] = $oFieldSQLExp;
|
||||
$oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sClass, $sAttCode, '', $oFieldSQLExp, $oBaseSQLQuery);
|
||||
}
|
||||
$aTranslation[$sClassAlias][$sAttCode] = $oFieldSQLExp;
|
||||
}
|
||||
|
||||
// Translate the selected columns
|
||||
|
||||
@@ -813,7 +813,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'UI:Delete:WillBeDeletedAutomatically' => 'Zal automatisch verwijderd worden',
|
||||
'UI:Delete:MustBeDeletedManually' => 'Moet handmatig verwijderd worden',
|
||||
'UI:Delete:CannotUpdateBecause_Issue' => 'Zou automatisch moeten geüpdatet worden, maar: %1$s',
|
||||
'UI:Delete:WillAutomaticallyUpdate_Fields' => 'Zal automatisch aangeapst worden (reset: %1$s)',
|
||||
'UI:Delete:WillAutomaticallyUpdate_Fields' => 'Zal automatisch aangepast worden (reset: %1$s)',
|
||||
'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d objecten/links verwijzen naar %2$s',
|
||||
'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d objecten/links verwijzen naar sommige objecten die verwijderd worden',
|
||||
'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'Elke verdere verwijzing moet verwijderd worden om de integriteit van de database te verzekeren',
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{% if bFailedLogin %}
|
||||
<p class="hilite">{{ sMessage }}</p>
|
||||
{% else %}
|
||||
<p>{{ sMessage }}</p>
|
||||
<p>{{ sMessage|raw }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock login_title %}
|
||||
|
||||
@@ -92,7 +92,31 @@ class ItopTestCase extends TestCase
|
||||
{
|
||||
$sId = str_replace('"', '', $this->getName());
|
||||
$sId = str_replace(' ', '_', $sId);
|
||||
|
||||
return $sId;
|
||||
}
|
||||
|
||||
public function InvokeNonPublicStaticMethod($sObjectClass, $sMethodName, $aArgs)
|
||||
{
|
||||
return $this->InvokeNonPublicMethod($sObjectClass, $sMethodName, null, $aArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sObjectClass for example DBObject::class
|
||||
* @param string $sMethodName
|
||||
* @param object $oObject
|
||||
* @param array $aArgs
|
||||
*
|
||||
* @return mixed method result
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function InvokeNonPublicMethod($sObjectClass, $sMethodName, $oObject, $aArgs)
|
||||
{
|
||||
$class = new \ReflectionClass($sObjectClass);
|
||||
$method = $class->getMethod($sMethodName);
|
||||
$method->setAccessible(true);
|
||||
|
||||
return $method->invokeArgs($oObject, $aArgs);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,9 @@ class DBSearchUpdateRealiasingMapTest extends ItopDataTestCase
|
||||
*/
|
||||
public function testUpdateRealiasingMap($aRealiasingMap, $aAliasTranslation, $aExpectedRealiasingMap)
|
||||
{
|
||||
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
|
||||
$oObject = new DBObjectSearch('Organization');
|
||||
$aArgs = [&$aRealiasingMap, $aAliasTranslation];
|
||||
$this->InvokeNonPublicMethod(DBObjectSearch::class, 'UpdateRealiasingMap', $oObject, $aArgs);
|
||||
$this->assertEquals($aExpectedRealiasingMap, $aRealiasingMap);
|
||||
}
|
||||
|
||||
@@ -77,12 +79,4 @@ class DBSearchUpdateRealiasingMapTest extends ItopDataTestCase
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function UpdateRealiasingMap(&$aRealiasingMap, $aAliasTranslation)
|
||||
{
|
||||
$class = new \ReflectionClass(DBObjectSearch::class);
|
||||
$method = $class->getMethod('UpdateRealiasingMap');
|
||||
$method->setAccessible(true);
|
||||
$method->invokeArgs(new DBObjectSearch('Organization'), [&$aRealiasingMap, $aAliasTranslation]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use DBObjectSearch;
|
||||
use DBSearch;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use OqlInterpreter;
|
||||
use QueryBuilderContext;
|
||||
use SQLObjectQueryBuilder;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
@@ -51,6 +54,7 @@ class OQLTest extends ItopDataTestCase
|
||||
* @group itopConfigMgmt
|
||||
* @group itopRequestMgmt
|
||||
* @dataProvider NestedQueryProvider
|
||||
* @depends testOQLSetup
|
||||
*
|
||||
* @param $sQuery
|
||||
*
|
||||
@@ -410,44 +414,103 @@ class OQLTest extends ItopDataTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider bug3618Provider
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testBug3618($sOQL, $sExplanation)
|
||||
{
|
||||
$this->markTestSkipped();
|
||||
return;
|
||||
if (is_string($sExplanation)) {
|
||||
$this->debug($sExplanation);
|
||||
}
|
||||
|
||||
$oSearch = DBSearch::FromOQL($sOQL);
|
||||
$oSet = new \CMDBObjectSet($oSearch);
|
||||
$oSet->CountWithLimit(1);
|
||||
/**
|
||||
* @dataProvider GetOQLClassTreeProvider
|
||||
* @param $sOQL
|
||||
* @param $sExpectedOQL
|
||||
*/
|
||||
public function testGetOQLClassTree($sOQL, $sExpectedOQL)
|
||||
{
|
||||
$oFilter = DBSearch::FromOQL($sOQL);
|
||||
$aCountAttToLoad = array();
|
||||
$sMainClass = null;
|
||||
foreach ($oFilter->GetSelectedClasses() as $sClassAlias => $sClass)
|
||||
{
|
||||
$aCountAttToLoad[$sClassAlias] = array();
|
||||
if (empty($sMainClass))
|
||||
{
|
||||
$sMainClass = $sClass;
|
||||
}
|
||||
}
|
||||
$aModifierProperties = MetaModel::MakeModifierProperties($oFilter);
|
||||
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($oFilter);
|
||||
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties, null, null, null, $aCountAttToLoad);
|
||||
$sResultOQL = $oSQLObjectQueryBuilder->DebugOQLClassTree($oBuild);
|
||||
|
||||
static::assertEquals($sExpectedOQL, $sResultOQL);
|
||||
}
|
||||
|
||||
public function bug3618Provider()
|
||||
public function GetOQLClassTreeProvider()
|
||||
{
|
||||
return [
|
||||
'ok' => [
|
||||
'sOql' => "SELECT UserRequest WHERE private_log LIKE '%FOO : %'
|
||||
|
||||
UNION
|
||||
|
||||
SELECT Ticket WHERE private_log LIKE '%BAR : %'",
|
||||
'sExplanation' => null,
|
||||
'Bug 3660 1' => [
|
||||
"SELECT UserRequest AS U JOIN lnkContactToTicket AS l ON l.ticket_id=U.id JOIN Team AS T ON l.contact_id=T.id",
|
||||
"SELECT `U` FROM `UserRequest` AS `U`
|
||||
INNER JOIN `lnkContactToTicket` AS `l`
|
||||
ON `U`.`id` = `l`.`ticket_id`
|
||||
INNER JOIN `Team` AS `T`
|
||||
ON `l`.`contact_id` = `T`.`id`",
|
||||
],
|
||||
|
||||
'KO: different number of columns' => [
|
||||
'sOql' => "SELECT UserRequest WHERE private_log LIKE '%FOO : %'
|
||||
|
||||
UNION
|
||||
|
||||
SELECT Ticket ",
|
||||
'sExplanation' => 'The UNION is composed by two SELECT stmt, the 1st one has two columns the second one has only one column: BOOM!',
|
||||
'Bug 3660 2' => [
|
||||
"SELECT UserRequest AS U JOIN lnkContactToTicket AS l ON l.ticket_id=U.id JOIN Contact AS C ON l.contact_id=C.id",
|
||||
"SELECT `U` FROM `UserRequest` AS `U`
|
||||
INNER JOIN `lnkContactToTicket` AS `l`
|
||||
ON `U`.`id` = `l`.`ticket_id`",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider MakeSelectQueryForCountProvider
|
||||
*
|
||||
* @param $sOQL
|
||||
* @param $sExpectedSQL
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testMakeSelectQueryForCount($sOQL, $sExpectedSQL)
|
||||
{
|
||||
$oFilter = DBSearch::FromOQL($sOQL);
|
||||
// Avoid adding all the fields for counts or "group by" requests
|
||||
$aCountAttToLoad = array();
|
||||
$sMainClass = null;
|
||||
foreach ($oFilter->GetSelectedClasses() as $sClassAlias => $sClass) {
|
||||
$aCountAttToLoad[$sClassAlias] = array();
|
||||
if (empty($sMainClass)) {
|
||||
$sMainClass = $sClass;
|
||||
}
|
||||
}
|
||||
$sSQL = $oFilter->MakeSelectQuery([], [], $aCountAttToLoad, null, 0, 0, true);
|
||||
static::assertEquals($sExpectedSQL, $sSQL);
|
||||
}
|
||||
|
||||
public function MakeSelectQueryForCountProvider()
|
||||
{
|
||||
return [
|
||||
'Bug 3618' => [
|
||||
"SELECT UserRequest WHERE private_log LIKE '%Auteur : %' UNION SELECT UserRequest",
|
||||
"SELECT COUNT(*) AS COUNT FROM (SELECT
|
||||
1
|
||||
FROM (
|
||||
SELECT
|
||||
DISTINCT `UserRequest_Ticket`.`id` AS `UserRequestid`
|
||||
FROM
|
||||
`ticket` AS `UserRequest_Ticket`
|
||||
WHERE ((`UserRequest_Ticket`.`private_log` LIKE '%Auteur : %') AND COALESCE((`UserRequest_Ticket`.`finalclass` IN ('UserRequest')), 1))
|
||||
|
||||
UNION
|
||||
SELECT
|
||||
DISTINCT `UserRequest`.`id` AS `UserRequestid`
|
||||
FROM
|
||||
`ticket_request` AS `UserRequest`
|
||||
WHERE 1
|
||||
|
||||
) as __selects__
|
||||
) AS _union_alderaan_",
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user