diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index abe102033..debfa2029 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -535,9 +535,9 @@ class RestUtils { if (is_object($key)) { - if (isset($oCriteria->finalclass)) + if (isset($key->finalclass)) { - $sClass = $oCriteria->finalclass; + $sClass = $key->finalclass; if (!MetaModel::IsValidClass($sClass)) { throw new Exception("finalclass: Unknown class '$sClass'"); @@ -609,6 +609,10 @@ class RestUtils } $value = DBObjectSet::FromArray($sLnkClass, $aLinks); } + else + { + $value = $oAttDef->FromJSONToValue($value); + } } catch (Exception $e) { diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index d30d80e4d..92863ba01 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -426,6 +426,26 @@ abstract class AttributeDefinition return (string)$sValue; } + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + */ + public function GetForJSON($value) + { + // In most of the cases, that will be the expected behavior... + return $this->GetEditValue($value); + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + */ + public function FromJSONToValue($json) + { + // Passthrough in most of the cases + return $json; + } + /** * Override to display the value in the GUI */ @@ -3322,6 +3342,11 @@ class AttributeBlob extends AttributeDefinition public function GetDefaultValue() {return "";} public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);} + public function GetEditValue($sValue, $oHostObj = null) + { + return ''; + } + // Facilitate things: allow the user to Set the value from a string public function MakeRealValue($proposedValue, $oHostObj) @@ -3450,6 +3475,44 @@ class AttributeBlob extends AttributeDefinition { return ''; // Not exportable in XML, or as CDATA + some subtags ?? } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + */ + public function GetForJSON($value) + { + if ($value instanceOf ormDocument) + { + $aValues = array(); + $aValues['data'] = base64_encode($value->GetData()); + $aValues['mimetype'] = $value->GetMimeType(); + $aValues['filename'] = $value->GetFileName(); + } + else + { + $aValues = null; + } + return $aValues; + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + */ + public function FromJSONToValue($json) + { + if (isset($json->data)) + { + $data = base64_decode($json->data); + $value = new ormDocument($data, $json->mimetype, $json->filename); + } + else + { + $value = null; + } + return $value; + } } /** diff --git a/core/restservices.class.inc.php b/core/restservices.class.inc.php index 3d589e13b..025ae847e 100644 --- a/core/restservices.class.inc.php +++ b/core/restservices.class.inc.php @@ -99,14 +99,9 @@ class ObjectResult $value[] = $aLnkValues; } } - elseif ($oAttDef->IsExternalKey()) - { - $value = $oObject->Get($sAttCode); - } else { - // Still to be refined... - $value = $oObject->GetEditValue($sAttCode); + $value = $oAttDef->GetForJSON($oObject->Get($sAttCode)); } } return $value; @@ -147,22 +142,40 @@ class RestResultWithObjects extends RestResult * @param array $aFields An array of attribute codes. List of the attributes to be reported. * @return void */ - public function AddObject($iCode, $sMessage, $oObject = null, $aFields = null) + public function AddObject($iCode, $sMessage, $oObject, $aFields) { $oObjRes = new ObjectResult(); $oObjRes->code = $iCode; $oObjRes->message = $sMessage; - if ($oObject) + $oObjRes->class = get_class($oObject); + foreach ($aFields as $sAttCode) { - $oObjRes->class = get_class($oObject); - foreach ($aFields as $sAttCode) - { - $oObjRes->AddField($oObject, $sAttCode); - } + $oObjRes->AddField($oObject, $sAttCode); } - $this->objects[] = $oObjRes; + $sObjKey = get_class($oObject).'::'.$oObject->GetKey(); + $this->objects[$sObjKey] = $oObjRes; + } +} + +class RestResultWithRelations extends RestResultWithObjects +{ + public $relations; + + public function __construct() + { + parent::__construct(); + $this->relations = array(); + } + + public function AddRelation($sSrcKey, $sDestKey) + { + if (!array_key_exists($sSrcKey, $this->relations)) + { + $this->relations[$sSrcKey] = array(); + } + $this->relations[$sSrcKey][] = array('key' => $sDestKey); } } @@ -243,6 +256,10 @@ class CoreServices implements iRestServiceProvider 'verb' => 'core/delete', 'description' => 'Delete objects' ); + $aOps[] = array( + 'verb' => 'core/get_related', + 'description' => 'Get related objects through the specified relation' + ); } return $aOps; } @@ -360,6 +377,55 @@ class CoreServices implements iRestServiceProvider $this->DeleteObjects($oResult, $aObjects, $bSimulate); break; + case 'core/get_related': + $oResult = new RestResultWithRelations(); + $sClass = RestUtils::GetClass($aParams, 'class'); + $key = RestUtils::GetMandatoryParam($aParams, 'key'); + $sRelation = RestUtils::GetMandatoryParam($aParams, 'relation'); + $iMaxRecursionDepth = RestUtils::GetOptionalParam($aParams, 'depth', 20 /* = MAX_RECURSION_DEPTH */); + $aShowFields = array('id', 'friendlyname'); + + $oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key); + $aIndexByClass = array(); + while ($oObject = $oObjectSet->Fetch()) + { + $aRelated = array(); + $aGraph = array(); + $aIndexByClass[get_class($oObject)][$oObject->GetKey()] = null; + $oResult->AddObject(0, '', $oObject, $aShowFields); + $this->GetRelatedObjects($oObject, $sRelation, $iMaxRecursionDepth, $aRelated, $aGraph); + + foreach($aRelated as $sClass => $aObjects) + { + foreach($aObjects as $oRelatedObj) + { + $aIndexByClass[get_class($oRelatedObj)][$oRelatedObj->GetKey()] = null; + $oResult->AddObject(0, '', $oRelatedObj, $aShowFields); + } + } + foreach($aGraph as $sSrcKey => $aDestinations) + { + foreach ($aDestinations as $sDestKey) + { + $oResult->AddRelation($sSrcKey, $sDestKey); + } + } + } + if (count($aIndexByClass) > 0) + { + $aStats = array(); + foreach ($aIndexByClass as $sClass => $aIds) + { + $aStats[] = $sClass.'= '.count($aIds); + } + $oResult->message = "Scope: ".$oObjectSet->Count()."; Related objects: ".implode(', ', $aStats); + } + else + { + $oResult->message = "Nothing found"; + } + break; + default: // unknown operation: handled at a higher level } @@ -492,4 +558,38 @@ class CoreServices implements iRestServiceProvider $oResult->message = $sRes; } } + + /** + * Helper function to get the related objects up to the given depth along with the "graph" of the relation + * @param DBObject $oObject Starting point of the computation + * @param string $sRelation Code of the relation (i.e; 'impact', 'depends on'...) + * @param integer $iMaxRecursionDepth Maximum level of recursion + * @param Hash $aRelated Two dimensions hash of the already related objects: array( 'class' => array(key => )) + * @param Hash $aGraph Hash array for the topology of the relation: source => related: array('class:key' => array( DBObjects )) + * @param integer $iRecursionDepth Current level of recursion + */ + protected function GetRelatedObjects(DBObject $oObject, $sRelation, $iMaxRecursionDepth, &$aRelated, &$aGraph, $iRecursionDepth = 1) + { + // Avoid loops + if ((array_key_exists(get_class($oObject), $aRelated)) && (array_key_exists($oObject->GetKey(), $aRelated[get_class($oObject)]))) return; + // Stop at maximum recursion level + if ($iRecursionDepth > $iMaxRecursionDepth) return; + + $sSrcKey = get_class($oObject).'::'.$oObject->GetKey(); + $aNewRelated = array(); + $oObject->GetRelatedObjects($sRelation, 1, $aNewRelated); + foreach($aNewRelated as $sClass => $aObjects) + { + if (!array_key_exists($sSrcKey, $aGraph)) + { + $aGraph[$sSrcKey] = array(); + } + foreach($aObjects as $oRelatedObject) + { + $aRelated[$sClass][$oRelatedObject->GetKey()] = $oRelatedObject; + $aGraph[$sSrcKey][] = get_class($oRelatedObject).'::'.$oRelatedObject->GetKey(); + $this->GetRelatedObjects($oRelatedObject, $sRelation, $iMaxRecursionDepth, $aRelated, $aGraph, $iRecursionDepth+1); + } + } + } } diff --git a/webservices/itoprest.examples.php b/webservices/itoprest.examples.php index 733db94d5..7d4d6b542 100644 --- a/webservices/itoprest.examples.php +++ b/webservices/itoprest.examples.php @@ -150,6 +150,13 @@ $aOperations = array( ), 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) ), + array( + 'operation' => 'core/get_related', // operation code + 'class' => 'Server', + 'key' => 'SELECT Server', + 'relation' => 'impacts', // relation code + 'depth' => 4, // max recursion depth + ), ); $sUrl = "http://localhost/rest-services/webservices/rest.php?version=1.0";