restservices.class.inc.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <?php
  2. // Copyright (C) 2013 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * REST/json services
  20. *
  21. * Definition of common structures + the very minimum service provider (manage objects)
  22. *
  23. * @package REST Services
  24. * @copyright Copyright (C) 2013 Combodo SARL
  25. * @license http://opensource.org/licenses/AGPL-3.0
  26. * @api
  27. */
  28. /**
  29. * Element of the response formed by RestResultWithObjects
  30. *
  31. * @package REST Services
  32. */
  33. class ObjectResult
  34. {
  35. public $code;
  36. public $message;
  37. public $fields;
  38. /**
  39. * Default constructor
  40. */
  41. public function __construct()
  42. {
  43. $this->code = RestResult::OK;
  44. $this->message = '';
  45. $this->fields = array();
  46. }
  47. /**
  48. * Helper to make an output value for a given attribute
  49. *
  50. * @param DBObject $oObject The object being reported
  51. * @param string $sAttCode The attribute code (must be valid)
  52. * @return string A scalar representation of the value
  53. */
  54. protected function MakeResultValue(DBObject $oObject, $sAttCode)
  55. {
  56. if ($sAttCode == 'id')
  57. {
  58. $value = $oObject->GetKey();
  59. }
  60. else
  61. {
  62. $sClass = get_class($oObject);
  63. $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  64. if ($oAttDef instanceof AttributeLinkedSet)
  65. {
  66. $value = array();
  67. // Make the list of required attributes
  68. // - Skip attributes pointing to the current object (redundant data)
  69. // - Skip link sets refering to the current data (infinite recursion!)
  70. $aRelevantAttributes = array();
  71. $sLnkClass = $oAttDef->GetLinkedClass();
  72. foreach (MetaModel::ListAttributeDefs($sLnkClass) as $sLnkAttCode => $oLnkAttDef)
  73. {
  74. // Skip any attribute of the link that points to the current object
  75. //
  76. if ($sLnkAttCode == $oAttDef->GetExtKeyToMe()) continue;
  77. if (method_exists($oLnkAttDef, 'GetKeyAttCode'))
  78. {
  79. if ($oLnkAttDef->GetKeyAttCode() ==$oAttDef->GetExtKeyToMe()) continue;
  80. }
  81. $aRelevantAttributes[] = $sLnkAttCode;
  82. }
  83. // Iterate on the set and build an array of array of attcode=>value
  84. $oSet = $oObject->Get($sAttCode);
  85. while ($oLnk = $oSet->Fetch())
  86. {
  87. $aLnkValues = array();
  88. foreach ($aRelevantAttributes as $sLnkAttCode)
  89. {
  90. $aLnkValues[$sLnkAttCode] = $this->MakeResultValue($oLnk, $sLnkAttCode);
  91. }
  92. $value[] = $aLnkValues;
  93. }
  94. }
  95. elseif ($oAttDef->IsExternalKey())
  96. {
  97. $value = $oObject->Get($sAttCode);
  98. }
  99. else
  100. {
  101. // Still to be refined...
  102. $value = $oObject->GetEditValue($sAttCode);
  103. }
  104. }
  105. return $value;
  106. }
  107. /**
  108. * Report the value for the given object attribute
  109. *
  110. * @param DBObject $oObject The object being reported
  111. * @param string $sAttCode The attribute code (must be valid)
  112. * @return void
  113. */
  114. public function AddField(DBObject $oObject, $sAttCode)
  115. {
  116. $this->fields[$sAttCode] = $this->MakeResultValue($oObject, $sAttCode);
  117. }
  118. }
  119. /**
  120. * REST response for services managing objects. Derive this structure to add information and/or constants
  121. *
  122. * @package Extensibility
  123. * @package REST Services
  124. * @api
  125. */
  126. class RestResultWithObjects extends RestResult
  127. {
  128. public $objects;
  129. /**
  130. * Report the given object
  131. *
  132. * @param int An error code (RestResult::OK is no issue has been found)
  133. * @param string $sMessage Description of the error if any, an empty string otherwise
  134. * @param DBObject $oObject The object being reported
  135. * @param array $aFields An array of attribute codes. List of the attributes to be reported.
  136. * @return void
  137. */
  138. public function AddObject($iCode, $sMessage, $oObject = null, $aFields = null)
  139. {
  140. $oObjRes = new ObjectResult();
  141. $oObjRes->code = $iCode;
  142. $oObjRes->message = $sMessage;
  143. if ($oObject)
  144. {
  145. foreach ($aFields as $sAttCode)
  146. {
  147. $oObjRes->AddField($oObject, $sAttCode);
  148. }
  149. }
  150. $this->objects[] = $oObjRes;
  151. }
  152. }
  153. /**
  154. * Implementation of core REST services (create/get/update... objects)
  155. *
  156. * @package Core
  157. */
  158. class CoreServices implements iRestServiceProvider
  159. {
  160. /**
  161. * Enumerate services delivered by this class
  162. *
  163. * @param string $sVersion The version (e.g. 1.0) supported by the services
  164. * @return array An array of hash 'verb' => verb, 'description' => description
  165. */
  166. public function ListOperations($sVersion)
  167. {
  168. $aOps = array();
  169. if ($sVersion == '1.0')
  170. {
  171. $aOps[] = array(
  172. 'verb' => 'core/create',
  173. 'description' => 'Create an object'
  174. );
  175. $aOps[] = array(
  176. 'verb' => 'core/update',
  177. 'description' => 'Update an object'
  178. );
  179. $aOps[] = array(
  180. 'verb' => 'core/apply_stimulus',
  181. 'description' => 'Apply a stimulus to change the state of an object'
  182. );
  183. $aOps[] = array(
  184. 'verb' => 'core/get',
  185. 'description' => 'Search for objects'
  186. );
  187. }
  188. return $aOps;
  189. }
  190. /**
  191. * Enumerate services delivered by this class
  192. * @param string $sVersion The version (e.g. 1.0) supported by the services
  193. * @return RestResult The standardized result structure (at least a message)
  194. * @throws Exception in case of internal failure.
  195. */
  196. public function ExecOperation($sVersion, $sVerb, $aParams)
  197. {
  198. $oResult = new RestResultWithObjects();
  199. switch ($sVerb)
  200. {
  201. case 'core/create':
  202. RestUtils::InitTrackingComment($aParams);
  203. $sClass = RestUtils::GetClass($aParams, 'class');
  204. $aFields = RestUtils::GetMandatoryParam($aParams, 'fields');
  205. $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
  206. $oObject = RestUtils::MakeObjectFromFields($sClass, $aFields);
  207. $oObject->DBInsert();
  208. $oResult->AddObject(0, 'created', $oObject, $aShowFields);
  209. break;
  210. case 'core/update':
  211. RestUtils::InitTrackingComment($aParams);
  212. $sClass = RestUtils::GetClass($aParams, 'class');
  213. $key = RestUtils::GetMandatoryParam($aParams, 'key');
  214. $aFields = RestUtils::GetMandatoryParam($aParams, 'fields');
  215. $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
  216. $oObject = RestUtils::FindObjectFromKey($sClass, $key);
  217. RestUtils::UpdateObjectFromFields($oObject, $aFields);
  218. $oObject->DBUpdate();
  219. $oResult->AddObject(0, 'updated', $oObject, $aShowFields);
  220. break;
  221. case 'core/apply_stimulus':
  222. RestUtils::InitTrackingComment($aParams);
  223. $sClass = RestUtils::GetClass($aParams, 'class');
  224. $key = RestUtils::GetMandatoryParam($aParams, 'key');
  225. $aFields = RestUtils::GetMandatoryParam($aParams, 'fields');
  226. $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
  227. $sStimulus = RestUtils::GetMandatoryParam($aParams, 'stimulus');
  228. $oObject = RestUtils::FindObjectFromKey($sClass, $key);
  229. RestUtils::UpdateObjectFromFields($oObject, $aFields);
  230. $aTransitions = $oObject->EnumTransitions();
  231. $aStimuli = MetaModel::EnumStimuli(get_class($oObject));
  232. if (!isset($aTransitions[$sStimulus]))
  233. {
  234. // Invalid stimulus
  235. $oResult->code = RestResult::INTERNAL_ERROR;
  236. $oResult->message = "Invalid stimulus: '$sStimulus' on the object ".$oObject->GetName()." in state '".$oObject->GetState()."'";
  237. }
  238. else
  239. {
  240. $aTransition = $aTransitions[$sStimulus];
  241. $sTargetState = $aTransition['target_state'];
  242. $aStates = MetaModel::EnumStates($sClass);
  243. $aTargetStateDef = $aStates[$sTargetState];
  244. $aExpectedAttributes = $aTargetStateDef['attribute_list'];
  245. $aMissingMandatory = array();
  246. foreach($aExpectedAttributes as $sAttCode => $iExpectCode)
  247. {
  248. if ( ($iExpectCode & OPT_ATT_MANDATORY) && ($oObject->Get($sAttCode) == ''))
  249. {
  250. $aMissingMandatory[] = $sAttCode;
  251. }
  252. }
  253. if (count($aMissingMandatory) == 0)
  254. {
  255. // If all the mandatory fields are already present, just apply the transition silently...
  256. if ($oObject->ApplyStimulus($sStimulus))
  257. {
  258. $oObject->DBUpdate();
  259. $oResult->AddObject(0, 'updated', $oObject, $aShowFields);
  260. }
  261. }
  262. else
  263. {
  264. // Missing mandatory attributes for the transition
  265. $oResult->code = RestResult::INTERNAL_ERROR;
  266. $oResult->message = 'Missing mandatory attribute(s) for applying the stimulus: '.implode(', ', $aMissingMandatory).'.';
  267. }
  268. }
  269. break;
  270. case 'core/get':
  271. $sClass = RestUtils::GetClass($aParams, 'class');
  272. $key = RestUtils::GetMandatoryParam($aParams, 'key');
  273. $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
  274. $oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key);
  275. while ($oObject = $oObjectSet->Fetch())
  276. {
  277. $oResult->AddObject(0, '', $oObject, $aShowFields);
  278. }
  279. $oResult->message = "Found: ".$oObjectSet->Count();
  280. break;
  281. default:
  282. // unknown operation: handled at a higher level
  283. }
  284. return $oResult;
  285. }
  286. }