diff --git a/application/applicationcontext.class.inc.php b/application/applicationcontext.class.inc.php index 374b6ceb0..0d5f27f4d 100644 --- a/application/applicationcontext.class.inc.php +++ b/application/applicationcontext.class.inc.php @@ -303,5 +303,58 @@ class ApplicationContext return ''; } } + + protected static $m_aPluginProperties = null; + + /** + * Load plugin properties for the current session + * @return void + */ + protected static function LoadPluginProperties() + { + if (isset($_SESSION['PluginProperties'])) + { + self::$m_aPluginProperties = $_SESSION['PluginProperties']; + } + else + { + self::$m_aPluginProperties = array(); + } + } + + /** + * Set plugin properties + * @param sPluginClass string Class implementing any plugin interface + * @param sProperty string Name of the property + * @param value scalar Value (numeric or string) + * @return void + */ + public static function SetPluginProperty($sPluginClass, $sProperty, $value) + { + if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties(); + + self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value; + $_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value; + } + + /** + * Get plugin properties + * @param sPluginClass string Class implementing any plugin interface + * @return array of sProperty=>value pairs + */ + public static function GetPluginProperties($sPluginClass) + { + if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties(); + + if (array_key_exists($sPluginClass, self::$m_aPluginProperties)) + { + return self::$m_aPluginProperties[$sPluginClass]; + } + else + { + return array(); + } + } + } ?> diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index a31b8ede9..5c0aa9051 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -93,6 +93,23 @@ abstract class CMDBObject extends DBObject protected static $m_oCurrChange = null; + public static function SetCurrentChange(CMDBChange $oChange) + { + self::$m_oCurrChange = $oChange; + } + + // + // Todo: simplify the APIs and do not pass the current change as an argument anymore + // SetCurrentChange to be invoked in very few cases (UI.php, CSV import, Data synchro) + // GetCurrentChange to be called ONCE (!) by CMDBChangeOp::OnInsert ($this->Set('change', ..GetCurrentChange()) + // GetCurrentChange to create a default change if not already done in the current context + // + public static function GetCurrentChange() + { + return self::$m_oCurrChange; + } + + private function RecordObjCreation(CMDBChange $oChange) { $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpCreate"); diff --git a/core/dbobject.class.php b/core/dbobject.class.php index a87796a94..cc99af76b 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1302,6 +1302,11 @@ abstract class DBObject return $this->DBInsert(); } + public function DBInsertTrackedNoReload(CMDBChange $oVoid) + { + return $this->DBInsertNoReload(); + } + // Creates a copy of the current object into the database // Returns the id of the newly created object public function DBClone($iNewKey = null) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 0d6c54247..a8add04e2 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -66,6 +66,8 @@ class DBObjectSearch $this->m_aRelatedTo = array(); $this->m_bDataFiltered = false; $this->m_aParentConditions = array(); + + $this->m_aModifierProperties = array(); } public function AllowAllData() {$this->m_bAllowAllData = true;} @@ -126,6 +128,23 @@ class DBObjectSearch } + public function SetModifierProperty($sPluginClass, $sProperty, $value) + { + $this->m_aModifierProperties[$sPluginClass][$sProperty] = $value; + } + + public function GetModifierProperties($sPluginClass) + { + if (array_key_exists($sPluginClass, $this->m_aModifierProperties)) + { + return $this->m_aModifierProperties[$sPluginClass]; + } + else + { + return array(); + } + } + public function IsAny() { // #@# todo - if (!$this->m_oSearchCondition->IsTrue()) return false; diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php index ad2590ca9..75f6184fa 100644 --- a/core/expression.class.inc.php +++ b/core/expression.class.inc.php @@ -953,10 +953,10 @@ class QueryBuilderExpressions protected $m_aSelectExpr; protected $m_aJoinFields; - public function __construct($aSelect, $oCondition) + public function __construct($oCondition) { $this->m_oConditionExpr = $oCondition; - $this->m_aSelectExpr = $aSelect; + $this->m_aSelectExpr = array(); $this->m_aJoinFields = array(); } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 6b7c97bf1..f66eb8b40 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -15,6 +15,8 @@ // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA require_once(APPROOT.'core/modulehandler.class.inc.php'); +require_once(APPROOT.'core/querybuildercontext.class.inc.php'); +require_once(APPROOT.'core/querymodifier.class.inc.php'); /** * Metamodel @@ -25,8 +27,6 @@ require_once(APPROOT.'core/modulehandler.class.inc.php'); * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ - - // #@# todo: change into class const (see Doctrine) // Doctrine example // class toto @@ -1404,7 +1404,7 @@ abstract class MetaModel // Build the list of available extensions // - $aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension'); + $aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iQueryModifier'); foreach($aInterfaces as $sInterface) { self::$m_aExtensionClasses[$sInterface] = array(); @@ -1924,12 +1924,26 @@ abstract class MetaModel } } + // Compute query modifiers properties (can be set in the search itself, by the context, etc.) + // + $aModifierProperties = self::MakeModifierProperties($oFilter); + if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries) { // Need to identify the query $sOqlQuery = $oFilter->ToOql(); - $sRawId = $sOqlQuery; + if (count($aModifierProperties)) + { + array_multisort($aModifierProperties); + $sModifierProperties = json_encode($aModifierProperties); + } + else + { + $sModifierProperties = ''; + } + + $sRawId = $sOqlQuery.$sModifierProperties; if (!is_null($aAttToLoad)) { foreach($aAttToLoad as $sAlias => $aAttributes) @@ -2024,12 +2038,10 @@ abstract class MetaModel if (!isset($oSelect)) { - $aClassAliases = array(); - $aTableAliases = array(); - $oQBExpr = new QueryBuilderExpressions(array(), $oFilter->GetCriteria()); + $oBuild = new QueryBuilderContext($oFilter, $aModifierProperties); $oKPI = new ExecutionKPI(); - $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, $aAttToLoad, array(), true /* main query */); + $oSelect = self::MakeQuery($oBuild, $oFilter, $aAttToLoad, array(), true /* main query */); $oSelect->SetSourceOQL($sOqlQuery); $oKPI->ComputeStats('MakeQuery (select)', $sOqlQuery); @@ -2138,12 +2150,33 @@ abstract class MetaModel } } + protected static function MakeModifierProperties($oFilter) + { + // Compute query modifiers properties (can be set in the search itself, by the context, etc.) + // + $aModifierProperties = array(); + foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier) + { + // Lowest precedence: the application context + $aPluginProps = ApplicationContext::GetPluginProperties($sPluginClass); + // Highest precedence: programmatically specified (or OQL) + foreach($oFilter->GetModifierProperties($sPluginClass) as $sProp => $value) + { + $aPluginProps[$sProp] = $value; + } + if (count($aPluginProps) > 0) + { + $aModifierProperties[$sPluginClass] = $aPluginProps; + } + } + return $aModifierProperties; + } + public static function MakeDeleteQuery(DBObjectSearch $oFilter, $aArgs = array()) { - $aClassAliases = array(); - $aTableAliases = array(); - $oQBExpr = new QueryBuilderExpressions(array(), $oFilter->GetCriteria()); - $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, null, array(), true /* main query */); + $aModifierProperties = self::MakeModifierProperties($oFilter); + $oBuild = new QueryBuilderContext($oFilter, $aModifierProperties); + $oSelect = self::MakeQuery($oBuild, $oFilter, null, array(), true /* main query */); $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); return $oSelect->RenderDelete($aScalarArgs); } @@ -2151,26 +2184,21 @@ abstract class MetaModel public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues, $aArgs = array()) { // $aValues is an array of $sAttCode => $value - $aClassAliases = array(); - $aTableAliases = array(); - $oQBExpr = new QueryBuilderExpressions(array(), $oFilter->GetCriteria()); - $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, null, $aValues, true /* main query */); + $aModifierProperties = self::MakeModifierProperties($oFilter); + $oBuild = new QueryBuilderContext($oFilter, $aModifierProperties); + $oSelect = self::MakeQuery($oBuild, $oFilter, null, $aValues, true /* main query */); $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); return $oSelect->RenderUpdate($aScalarArgs); } - private static function MakeQuery($aSelectedClasses, &$oQBExpr, &$aClassAliases, &$aTableAliases, DBObjectSearch $oFilter, $aAttToLoad = null, $aValues = array(), $bIsMainQuery = false) + private static function MakeQuery(&$oBuild, DBObjectSearch $oFilter, $aAttToLoad = null, $aValues = array(), $bIsMainQuery = false) { // Note: query class might be different than the class of the filter // -> this occurs when we are linking our class to an external class (referenced by, or pointing to) $sClass = $oFilter->GetFirstJoinedClass(); $sClassAlias = $oFilter->GetFirstJoinedClassAlias(); - $bIsOnQueriedClass = array_key_exists($sClassAlias, $aSelectedClasses); - if ($bIsOnQueriedClass) - { - $aClassAliases = array_merge($aClassAliases, $oFilter->GetJoinedClasses()); - } + $bIsOnQueriedClass = array_key_exists($sClassAlias, $oBuild->GetRootFilter()->GetSelectedClasses()); self::DbgTrace("Entering: ".$oFilter->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY")); @@ -2180,7 +2208,7 @@ abstract class MetaModel if ($bIsOnQueriedClass) { // default to the whole list of attributes + the very std id/finalclass - $oQBExpr->AddSelect($sClassAlias.'id', new FieldExpression('id', $sClassAlias)); + $oBuild->m_oQBExpressions->AddSelect($sClassAlias.'id', new FieldExpression('id', $sClassAlias)); if (is_null($aAttToLoad) || !array_key_exists($sClassAlias, $aAttToLoad)) { @@ -2196,7 +2224,7 @@ abstract class MetaModel foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) { - $oQBExpr->AddSelect($sClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias)); + $oBuild->m_oQBExpressions->AddSelect($sClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias)); } } @@ -2216,14 +2244,14 @@ abstract class MetaModel foreach($aFullText as $sFTNeedle) { $oNewCond = new BinaryExpression($oTextFields, 'LIKE', new ScalarExpression("%$sFTNeedle%")); - $oQBExpr->AddCondition($oNewCond); + $oBuild->m_oQBExpressions->AddCondition($oNewCond); } } } -//echo "
oQBExpr ".__LINE__.":
\n".print_r($oQBExpr, true)."\n"; +//echo "
oQBExpr ".__LINE__.":
\n".print_r($oBuild->m_oQBExpressions, true)."\n"; $aExpectedAtts = array(); // array of (attcode => fieldexpression) //echo "
".__LINE__.": GetUnresolvedFields($sClassAlias, ...)
\n"; - $oQBExpr->GetUnresolvedFields($sClassAlias, $aExpectedAtts); + $oBuild->m_oQBExpressions->GetUnresolvedFields($sClassAlias, $aExpectedAtts); // Compute a clear view of required joins (from the current class) // Build the list of external keys: @@ -2260,9 +2288,9 @@ abstract class MetaModel { $aTranslateNow = array(); $aTranslateNow[$sClassAlias]['friendlyname'] = self::GetNameExpression($sClass, $sClassAlias); -//echo "oQBExpr ".__LINE__.":
\n".print_r($oQBExpr, true)."\n"; - $oQBExpr->Translate($aTranslateNow, false); -//echo "
oQBExpr ".__LINE__.":
\n".print_r($oQBExpr, true)."\n"; +//echo "
oQBExpr ".__LINE__.":
\n".print_r($oBuild->m_oQBExpressions, true)."\n"; + $oBuild->m_oQBExpressions->Translate($aTranslateNow, false); +//echo "
oQBExpr ".__LINE__.":
\n".print_r($oBuild->m_oQBExpressions, true)."\n"; $aNameSpec = self::GetNameSpec($sClass); foreach($aNameSpec[1] as $i => $sAttCode) @@ -2304,7 +2332,7 @@ abstract class MetaModel self::DbgTrace("Main (=leaf) class, call MakeQuerySingleTable()"); if (self::HasTable($sClass)) { - $oSelectBase = self::MakeQuerySingleTable($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, $sClass, $aExtKeys, $aValues); + $oSelectBase = self::MakeQuerySingleTable($oBuild, $oFilter, $sClass, $aExtKeys, $aValues); } else { @@ -2313,7 +2341,7 @@ abstract class MetaModel // As the join will not filter on the expected classes, we have to specify it explicitely $sExpectedClasses = implode("', '", self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); $oFinalClassRestriction = Expression::FromOQL("`$sClassAlias`.finalclass IN ('$sExpectedClasses')"); - $oQBExpr->AddCondition($oFinalClassRestriction); + $oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction); } // Then we join the queries of the eventual parent classes (compound model) @@ -2322,7 +2350,7 @@ abstract class MetaModel if (!self::HasTable($sParentClass)) continue; //echo "
Parent class: $sParentClass... let's call MakeQuerySingleTable()
"; self::DbgTrace("Parent class: $sParentClass... let's call MakeQuerySingleTable()"); - $oSelectParentTable = self::MakeQuerySingleTable($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, $sParentClass, $aExtKeys, $aValues); + $oSelectParentTable = self::MakeQuerySingleTable($oBuild, $oFilter, $sParentClass, $aExtKeys, $aValues); if (is_null($oSelectBase)) { $oSelectBase = $oSelectParentTable; @@ -2347,11 +2375,11 @@ abstract class MetaModel //self::DbgTrace($oSelectForeign->RenderSelect(array())); $sForeignClassAlias = $oForeignFilter->GetFirstJoinedClassAlias(); - $oQBExpr->PushJoinField(new FieldExpression($sForeignKeyAttCode, $sForeignClassAlias)); + $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sForeignKeyAttCode, $sForeignClassAlias)); - $oSelectForeign = self::MakeQuery($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oForeignFilter, $aAttToLoad); + $oSelectForeign = self::MakeQuery($oBuild, $oForeignFilter, $aAttToLoad); - $oJoinExpr = $oQBExpr->PopJoinField(); + $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); $sForeignKeyTable = $oJoinExpr->GetParent(); $sForeignKeyColumn = $oJoinExpr->GetName(); $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); @@ -2390,8 +2418,8 @@ abstract class MetaModel // if ($bIsMainQuery) { - $oSelectBase->SetCondition($oQBExpr->GetCondition()); - $oSelectBase->SetSelect($oQBExpr->GetSelect()); + $oSelectBase->SetCondition($oBuild->m_oQBExpressions->GetCondition()); + $oSelectBase->SetSelect($oBuild->m_oQBExpressions->GetSelect()); } // That's all... cross fingers and we'll get some working query @@ -2402,7 +2430,7 @@ abstract class MetaModel return $oSelectBase; } - protected static function MakeQuerySingleTable($aSelectedClasses, &$oQBExpr, &$aClassAliases, &$aTableAliases, $oFilter, $sTableClass, $aExtKeys, $aValues) + protected static function MakeQuerySingleTable(&$oBuild, $oFilter, $sTableClass, $aExtKeys, $aValues) { // $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields)) //echo "MAKEQUERY($sTableClass)-liste des clefs externes($sTableClass):".print_r($aExtKeys, true)."
oQBExpr ".__LINE__.":
\n".print_r($oQBExpr, true)."\n".print_r($aTranslateNow, true)."\n"; - $oQBExpr->Translate($aTranslateNow, false); -//echo "
oQBExpr ".__LINE__.":
\n".print_r($oQBExpr, true)."\n"; +//echo "
oQBExpr ".__LINE__.":
\n".print_r($oBuild->m_oQBExpressions, true)."\n".print_r($aTranslateNow, true)."\n"; + $oBuild->m_oQBExpressions->Translate($aTranslateNow, false); +//echo "
oQBExpr ".__LINE__.":
\n".print_r($oBuild->m_oQBExpressions, true)."\n"; //echo "
External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()/p>\n"; self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()"); - $oQBExpr->PushJoinField(new FieldExpression('id', $sKeyClassAlias)); + $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression('id', $sKeyClassAlias)); -//echo "
Recursive MakeQuery ".__LINE__.":
\n".print_r($aSelectedClasses, true)."\n"; - $oSelectExtKey = self::MakeQuery($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oExtFilter); +//echo "
Recursive MakeQuery ".__LINE__.":
\n".print_r($oBuild->GetRootFilter()->GetSelectedClasses(), true)."\n"; + $oSelectExtKey = self::MakeQuery($oBuild, $oExtFilter); - $oJoinExpr = $oQBExpr->PopJoinField(); + $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); $sExternalKeyTable = $oJoinExpr->GetParent(); $sExternalKeyField = $oJoinExpr->GetName(); @@ -2604,9 +2645,9 @@ abstract class MetaModel } elseif(self::$m_aAttribOrigins[$sKeyClass][$sKeyAttCode] == $sTableClass) { - $oQBExpr->PushJoinField(new FieldExpression($sKeyAttCode, $sKeyClassAlias)); - $oSelectExtKey = self::MakeQuery($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oExtFilter); - $oJoinExpr = $oQBExpr->PopJoinField(); + $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sKeyAttCode, $sKeyClassAlias)); + $oSelectExtKey = self::MakeQuery($oBuild, $oExtFilter); + $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); //echo "MAKEQUERY-PopJoinField pour $sKeyAttCode, $sKeyClassAlias:
".print_r($oJoinExpr, true)."
oQBExpr ".__LINE__.":
\n".print_r($oQBExpr, true)."\n"; - $oQBExpr->Translate($aTranslation, false); -//echo "
oQBExpr ".__LINE__.":
\n".print_r($oQBExpr, true)."\n"; +//echo "
oQBExpr ".__LINE__.":
\n".print_r($oBuild->m_oQBExpressions, true)."\n"; + $oBuild->m_oQBExpressions->Translate($aTranslation, false); +//echo "
oQBExpr ".__LINE__.":
\n".print_r($oBuild->m_oQBExpressions, true)."\n"; //MyHelpers::var_dump_html($oSelectBase->RenderSelect()); return $oSelectBase; @@ -4660,7 +4701,7 @@ abstract class MetaModel } /** - * Returns an array of classes implementing the given interface + * Returns an array of classes=>instance implementing the given interface */ public static function EnumPlugins($sInterface) { @@ -4743,5 +4784,4 @@ MetaModel::RegisterZList("preview", array("description"=>"All attributes visible MetaModel::RegisterZList("standard_search", array("description"=>"List of criteria for the standard search", "type"=>"filters")); MetaModel::RegisterZList("advanced_search", array("description"=>"List of criteria for the advanced search", "type"=>"filters")); - -?> +?> \ No newline at end of file diff --git a/core/querybuildercontext.class.inc.php b/core/querybuildercontext.class.inc.php new file mode 100644 index 000000000..8c6e88544 --- /dev/null +++ b/core/querybuildercontext.class.inc.php @@ -0,0 +1,74 @@ + MakeQuery/MakeQuerySingleTable + * + * @author Erwan Taloc