diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index fff7c2338..9b57078a8 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -63,9 +63,15 @@ class DBObjectSearch extends DBSearch { parent::__construct(); - if (is_null($sClassAlias)) $sClassAlias = $sClass; - if(!is_string($sClass)) throw new Exception('DBObjectSearch::__construct called with a non-string parameter: $sClass = '.print_r($sClass, true)); - if(!MetaModel::IsValidClass($sClass)) throw new Exception('DBObjectSearch::__construct called for an invalid class: "'.$sClass.'"'); + if (is_null($sClassAlias)) { + $sClassAlias = $sClass; + } + if (!is_string($sClass)) { + throw new Exception('DBObjectSearch::__construct called with a non-string parameter: $sClass = '.print_r($sClass, true)); + } + if (!MetaModel::IsValidClass($sClass)) { + throw new Exception('DBObjectSearch::__construct called for an invalid class: "'.$sClass.'"'); + } $this->m_aSelectedClasses = array($sClassAlias => $sClass); $this->m_aClasses = array($sClassAlias => $sClass); @@ -75,30 +81,43 @@ class DBObjectSearch extends DBSearch $this->m_aReferencedBy = array(); } - public function AllowAllData($bAllowAllData = true) {$this->m_bAllowAllData = $bAllowAllData;} - public function IsAllDataAllowed() {return $this->m_bAllowAllData;} - protected function IsDataFiltered() {return $this->m_bDataFiltered; } - protected function SetDataFiltered() {$this->m_bDataFiltered = true;} + public function AllowAllData($bAllowAllData = true) { + $this->m_bAllowAllData = $bAllowAllData; + + $this->m_oSearchCondition->Browse(function ($oThisExpression) use ($bAllowAllData) { + ExpressionHelper::ExpressionAllowAllDataCallback($oThisExpression, $bAllowAllData); + }); + } + + public function IsAllDataAllowed() { + return $this->m_bAllowAllData; + } + + protected function IsDataFiltered() { + return $this->m_bDataFiltered; + } + + protected function SetDataFiltered() { + $this->m_bDataFiltered = true; + } // Create a search definition that leads to 0 result, still a valid search object - static public function FromEmptySet($sClass) - { + public static function FromEmptySet($sClass) { $oResultFilter = new DBObjectSearch($sClass); $oResultFilter->m_oSearchCondition = new FalseExpression; + return $oResultFilter; } - public function GetJoinedClasses() {return $this->m_aClasses;} + public function GetJoinedClasses() { + return $this->m_aClasses; + } - public function GetClassName($sAlias) - { - if (array_key_exists($sAlias, $this->m_aSelectedClasses)) - { + public function GetClassName($sAlias) { + if (array_key_exists($sAlias, $this->m_aSelectedClasses)) { return $this->m_aSelectedClasses[$sAlias]; - } - else - { + } else { throw new CoreException("Invalid class alias '$sAlias'"); } } @@ -358,37 +377,35 @@ class DBObjectSearch extends DBSearch } foreach($this->m_aReferencedBy as $sForeignClass => $aReferences) { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $oForeignFilter) - { + foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) { + foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) { + foreach ($aFilters as $oForeignFilter) { $oForeignFilter->RenameParam($sOldName, $sNewName); } } } } } - - public function ResetCondition() - { + + public function ResetCondition() { $this->m_oSearchCondition = new TrueExpression(); // ? is that usefull/enough, do I need to rebuild the list after the subqueries ? } - public function MergeConditionExpression($oExpression) - { - $this->m_oSearchCondition = $this->m_oSearchCondition->LogOr($oExpression); + public function MergeConditionExpression($oExpression) { + $this->m_oSearchCondition = $this->m_oSearchCondition->LogOr($oExpression); } - public function AddConditionExpression($oExpression) - { - $this->m_oSearchCondition = $this->m_oSearchCondition->LogAnd($oExpression); + public function AddConditionExpression($oExpression) { + $this->m_oSearchCondition = $this->m_oSearchCondition->LogAnd($oExpression); + + $bRootSearchAllowAllData = $this->IsAllDataAllowed(); + $oExpression->Browse(function ($oThisExpression) use ($bRootSearchAllowAllData) { + ExpressionHelper::ExpressionAllowAllDataCallback($oThisExpression, $bRootSearchAllowAllData); + }); } - public function AddNameCondition($sName) - { + public function AddNameCondition($sName) { $oValueExpr = new ScalarExpression($sName); $oNameExpr = new FieldExpression('friendlyname', $this->GetClassAlias()); $oNewCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr); diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php index 596823041..69d04420d 100644 --- a/core/oql/expression.class.inc.php +++ b/core/oql/expression.class.inc.php @@ -1,32 +1,52 @@ -// +/* + * Copyright (C) 2010-2020 Combodo SARL + * + * This file is part of iTop. + * + * iTop 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. + * + * iTop 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 + */ -class MissingQueryArgument extends CoreException -{ +class MissingQueryArgument extends CoreException { +} + + +class ExpressionHelper { + /** + * Callback to be used with {@link Expression::Browse}, to update the AllowAllData attribute in the NestedQueryExpression that are + * present in the Expression tree + * + * @param \Expression $oExpression + * @param boolean $bAllowAllData + * + * @uses \DBSearch::AllowAllData() + * + * @since 2.7.2 2.8.0 N°3324 + */ + public static function ExpressionAllowAllDataCallback($oExpression, $bAllowAllData) { + if (!($oExpression instanceof NestedQueryExpression)) { + return; + } + + $oExpression->AllowAllData($bAllowAllData); + } } /** * @method Check($oModelReflection, array $aAliases, $sSourceQuery) */ -abstract class Expression -{ +abstract class Expression { const OPERATOR_BINARY = 'binary'; const OPERATOR_BOOLEAN = 'boolean_binary'; const OPERATOR_FIELD = 'field'; @@ -139,9 +159,13 @@ abstract class Expression } /** - * Recursively browse the expression tree - * @param Closure $callback - * @return mixed + * Recursively browse the expression tree. + * + * To access variables, specify them using the `use` keyword and the `&` to pass by reference if necessary + * + * @see https://www.php.net/manual/fr/functions.anonymous.php + * + * @param Closure $callback with current expression as parameter */ abstract public function Browse(Closure $callback); @@ -2157,60 +2181,62 @@ class NestedQueryExpression extends Expression } /**/ - public function ApplyParameters($aArgs) - { + public function ApplyParameters($aArgs) { $this->m_oNestedQuery->ApplyParameters($aArgs); } /**/ - public function GetUnresolvedFields($sAlias, &$aUnresolved) - { + public function GetUnresolvedFields($sAlias, &$aUnresolved) { } /**/ - public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true) - { + public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true) { // Check and prepare the select information - $this->m_oNestedQuery->TranslateConditions($aTranslationData, $bMatchAll , $bMarkFieldsAsResolved ); + $this->m_oNestedQuery->TranslateConditions($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved); + return clone $this; } - public function ListRequiredFields() - { + public function ListRequiredFields() { return array(); } - public function CollectUsedParents(&$aTable) - { + public function CollectUsedParents(&$aTable) { } - public function ListConstantFields() - { + public function ListConstantFields() { return $this->m_oNestedQuery->ListConstantFields(); } - public function ListParameters() - { + public function ListParameters() { return $this->m_oNestedQuery->ListParameters(); } - public function RenameParam($sOldName, $sNewName) - { + public function RenameParam($sOldName, $sNewName) { $this->m_oNestedQuery->RenameParam($sOldName, $sNewName); } - public function RenameAlias($sOldName, $sNewName) - { + public function RenameAlias($sOldName, $sNewName) { $this->m_oNestedQuery->RenameAlias($sOldName, $sNewName); } /** * @inheritDoc */ - public function ToJSON(&$aArgs = null, $bRetrofitParams = false) - { + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) { return $this->m_oNestedQuery->ToJSON(); } + + /** + * Simple indirection to {@link \DBObjectSearch::AllowAllData()} + * + * @param bool $bAllowAllData + * + * @uses \DBSearch::AllowAllData() + */ + public function AllowAllData($bAllowAllData = true) { + $this->m_oNestedQuery->AllowAllData($bAllowAllData); + } } class FunctionExpression extends Expression diff --git a/test/core/DBSearchTest.php b/test/core/DBSearchTest.php index 9389bc9ca..abc8a7795 100644 --- a/test/core/DBSearchTest.php +++ b/test/core/DBSearchTest.php @@ -675,17 +675,49 @@ class DBSearchTest extends ItopDataTestCase $TwoOrgIdsOnly = array($allOrgIds[0], $allOrgIds[1]); $oSearch = DBSearch::FromOQL("SELECT UserRequest WHERE org_id IN (:org_ids)"); self::assertNotNull($oSearch); - $oSet = new \CMDBObjectSet($oSearch, array(), array('org_ids'=> $TwoOrgIdsOnly)); + $oSet = new \CMDBObjectSet($oSearch, array(), array('org_ids' => $TwoOrgIdsOnly)); static::assertEquals(4, $oSet->Count()); - $_SERVER['REQUEST_URI']='FAKE_REQUEST_URI' ; - $_SERVER['REQUEST_METHOD']='FAKE_REQUEST_METHOD'; + $_SERVER['REQUEST_URI'] = 'FAKE_REQUEST_URI'; + $_SERVER['REQUEST_METHOD'] = 'FAKE_REQUEST_METHOD'; $oP = new \iTopWebPage("test"); $oBlock = new \DisplayBlock($oSet->GetFilter(), 'list', false); - $sHtml = $oBlock->GetDisplay($oP, 'package_table', array ('menu'=>true, 'display_limit'=>false)); + $sHtml = $oBlock->GetDisplay($oP, 'package_table', array('menu' => true, 'display_limit' => false)); $iHtmlUserRequestLineCount = substr_count($sHtml, 'output(); } + + /** + * @since 2.7.2 2.8.0 N°3324 + */ + public function testAllowAllData() { + $oSimpleSearch = \DBObjectSearch::FromOQL('SELECT FunctionalCI'); + $oSimpleSearch->AllowAllData(false); + self::assertFalse($oSimpleSearch->IsAllDataAllowed(), 'DBSearch AllowData value'); + $oSimpleSearch->AllowAllData(true); + self::assertTrue($oSimpleSearch->IsAllDataAllowed(), 'DBSearch AllowData value'); + + $sNestedQuery = 'SELECT FunctionalCI WHERE id IN (SELECT Server)'; + $this->CheckNestedSearch($sNestedQuery, true); + $this->CheckNestedSearch($sNestedQuery, false); + } + + private function CheckNestedSearch($sQuery, $bAllowAllData) { + $oNestedQuerySearch = \DBObjectSearch::FromOQL($sQuery); + $oNestedQuerySearch->AllowAllData($bAllowAllData); + self::assertEquals($bAllowAllData, $oNestedQuerySearch->IsAllDataAllowed(), 'root DBSearch AllowData value'); + $oNestedSearchInExpression = null; + $oNestedQuerySearch->GetCriteria()->Browse(function ($oExpression) use (&$oNestedSearchInExpression) { + if ($oExpression instanceof \NestedQueryExpression) { + $oNestedSearchInExpression = $oExpression->GetNestedQuery(); + + return; + } + }); + self::assertNotNull($oNestedSearchInExpression, 'We must have a DBSearch inside a NestedQueryExpression inside the root DBSearch'); + /** @var \DBObjectSearch $oNestedSearchInExpression */ + self::assertEquals($bAllowAllData, $oNestedSearchInExpression->IsAllDataAllowed(), 'Nested DBSearch AllowData value'); + } }