diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index ee3cd25d7..6da564d7f 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -1418,7 +1418,7 @@ class MenuBlock extends DisplayBlock $this->AddMenuSeparator($aActions); foreach($aRelations as $sRelationCode) { - $aActions[$sRelationCode] = array ('label' => MetaModel::GetRelationVerbUp($sRelationCode), 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&class=$sClass&id=$id{$sContext}"); + $aActions[$sRelationCode] = array ('label' => MetaModel::GetRelationLabel($sRelationCode), 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&class=$sClass&id=$id{$sContext}"); } } /* diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 034edacc3..184578fd9 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -2531,41 +2531,66 @@ abstract class DBObject implements iDisplay } // Return an empty set for the parent of all + // May be overloaded. + // Anyhow, this way of implementing the relations suffers limitations (not handling the redundancy) + // and you should consider defining those things in XML. public static function GetRelationQueries($sRelCode) { return array(); } + // Reserved: do not overload + public static function GetRelationQueriesEx($sRelCode) + { + return array(); + } + public function GetRelatedObjects($sRelCode, $iMaxDepth = 99, &$aResults = array()) { foreach (MetaModel::EnumRelationQueries(get_class($this), $sRelCode) as $sDummy => $aQueryInfo) { MetaModel::DbgTrace("object=".$this->GetKey().", depth=$iMaxDepth, rel=".$aQueryInfo["sQuery"]); $sQuery = $aQueryInfo["sQuery"]; - $bPropagate = $aQueryInfo["bPropagate"]; - $iDistance = $aQueryInfo["iDistance"]; + //$bPropagate = $aQueryInfo["bPropagate"]; + //$iDepth = $bPropagate ? $iMaxDepth - 1 : 0; + $iDepth = $iMaxDepth - 1; - $iDepth = $bPropagate ? $iMaxDepth - 1 : 0; - - $oFlt = DBObjectSearch::FromOQL($sQuery); - $oObjSet = new DBObjectSet($oFlt, array(), $this->ToArgsForQuery()); - while ($oObj = $oObjSet->Fetch()) + // Note: the loop over the result set has been written in an unusual way for error reporting purposes + // In the case of a wrong query parameter name, the error occurs on the first call to Fetch, + // thus we need to have this first call into the try/catch, but + // we do NOT want to nest the try/catch for the error message to be clear + try { - $sRootClass = MetaModel::GetRootClass(get_class($oObj)); - $sObjKey = $oObj->GetKey(); - if (array_key_exists($sRootClass, $aResults)) + $oFlt = DBObjectSearch::FromOQL($sQuery); + $oObjSet = new DBObjectSet($oFlt, array(), $this->ToArgsForQuery()); + $oObj = $oObjSet->Fetch(); + } + catch (Exception $e) + { + $sClassOfDefinition = $aQueryInfo['_legacy_'] ? get_class($this).'(or a parent)::GetRelationQueries()' : $aQueryInfo['sDefinedInClass']; + throw new Exception("Wrong query for the relation $sRelCode/$sClassOfDefinition/{$aQueryInfo['sNeighbour']}: ".$e->getMessage()); + } + if ($oObj) + { + do { - if (array_key_exists($sObjKey, $aResults[$sRootClass])) + $sRootClass = MetaModel::GetRootClass(get_class($oObj)); + $sObjKey = $oObj->GetKey(); + if (array_key_exists($sRootClass, $aResults)) { - continue; // already visited, skip + if (array_key_exists($sObjKey, $aResults[$sRootClass])) + { + continue; // already visited, skip + } + } + + $aResults[$sRootClass][$sObjKey] = $oObj; + if ($iDepth > 0) + { + $oObj->GetRelatedObjects($sRelCode, $iDepth, $aResults); } } - - $aResults[$sRootClass][$sObjKey] = $oObj; - if ($iDepth > 0) - { - $oObj->GetRelatedObjects($sRelCode, $iDepth, $aResults); - } + while ($oObj = $oObjSet->Fetch()); } } return $aResults; diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 3ec14f067..b39d67c4b 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1124,31 +1124,77 @@ abstract class MetaModel return $aResult; } - public static function EnumRelationProperties($sRelCode) - { - MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); - return self::$m_aRelationInfos[$sRelCode]; - } - final static public function GetRelationDescription($sRelCode) { return Dict::S("Relation:$sRelCode/Description"); } - final static public function GetRelationVerbUp($sRelCode) + final static public function GetRelationLabel($sRelCode) { return Dict::S("Relation:$sRelCode/VerbUp"); } - final static public function GetRelationVerbDown($sRelCode) - { - return Dict::S("Relation:$sRelCode/VerbDown"); - } - public static function EnumRelationQueries($sClass, $sRelCode) { MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); - return call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode)); + + $aNeighbours = call_user_func_array(array($sClass, 'GetRelationQueriesEx'), array($sRelCode)); + + // Translate attributes into queries (new style of spec only) + foreach($aNeighbours as $trash => &$aNeighbourData) + { + try + { + if (strlen($aNeighbourData['sQuery']) == 0) + { + $oAttDef = self::GetAttributeDef($sClass, $aNeighbourData['sAttribute']); + if ($oAttDef instanceof AttributeExternalKey) + { + $sTargetClass = $oAttDef->GetTargetClass(); + $aNeighbourData['sQuery'] = 'SELECT '.$sTargetClass.' AS o WHERE o.id = :this->'.$aNeighbourData['sAttribute']; + } + elseif ($oAttDef instanceof AttributeLinkedSet) + { + $sLinkedClass = $oAttDef->GetLinkedClass(); + $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); + if ($oAttDef->IsIndirect()) + { + $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); + $oRemoteAttDef = self::GetAttributeDef($sLinkedClass, $sExtKeyToRemote); + $sRemoteClass = $oRemoteAttDef->GetTargetClass(); + + $aNeighbourData['sQuery'] = "SELECT $sRemoteClass AS o JOIN $sLinkedClass AS lnk ON lnk.$sExtKeyToRemote = o.id WHERE lnk.$sExtKeyToMe = :this->id"; + } + else + { + $aNeighbourData['sQuery'] = "SELECT $sLinkedClass AS o WHERE o.$sExtKeyToMe = :this->id"; + } + } + else + { + throw new Exception("Unexpected attribute type for '{$aNeighbourData['sAttribute']}'. Expecting a link set or external key."); + } + } + } + catch (Exception $e) + { + $sClassOfDefinition = $aNeighbourData['_legacy_'] ? $sClass.'(or a parent)::GetRelationQueries()' : $aNeighbourData['sDefinedInClass']; + throw new Exception("Wrong definition for the relation $sRelCode/$sClassOfDefinition/{$aNeighbourData['sNeighbour']}: ".$e->getMessage()); + } + } + + // Merge legacy and new specs + $aLegacy = call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode)); + foreach($aLegacy as $sId => $aLegacyEntry) + { + $aLegacyEntry['_legacy_'] = true; + $aNeighbours[] = array( + '_legacy_' => true, + 'sQuery' => $aLegacyEntry['sQuery'], + 'sNeighbour' => $sId + ); + } + return $aNeighbours; } // diff --git a/pages/schema.php b/pages/schema.php index 102bafab3..3abbf2b73 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -341,8 +341,7 @@ function DisplayClassesList($oPage, $sContext) $oPage->add("