rest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. <?php
  2. // Copyright (C) 2010-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. * Entry point for all the REST services
  20. *
  21. * --------------------------------------------------
  22. * Create an object
  23. * --------------------------------------------------
  24. * POST itop/webservices/rest.php
  25. * {
  26. * operation: 'object_create',
  27. * comment: 'Synchronization from blah...',
  28. * class: 'UserRequest',
  29. * results: 'id, friendlyname',
  30. * fields:
  31. * {
  32. * org_id: 'SELECT Organization WHERE name = "Demo"',
  33. * caller_id:
  34. * {
  35. * name: 'monet',
  36. * first_name: 'claude',
  37. * }
  38. * title: 'Houston, got a problem!',
  39. * description: 'The fridge is empty'
  40. * contacts_list:
  41. * [
  42. * {
  43. * role: 'pizza delivery',
  44. * contact_id:
  45. * {
  46. * finalclass: 'Person',
  47. * name: 'monet',
  48. * first_name: 'claude'
  49. * }
  50. * }
  51. * ]
  52. * }
  53. * }
  54. *
  55. *
  56. * @copyright Copyright (C) 2010-2013 Combodo SARL
  57. * @license http://opensource.org/licenses/AGPL-3.0
  58. */
  59. if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
  60. require_once(__DIR__.'/../approot.inc.php');
  61. require_once(APPROOT.'/application/application.inc.php');
  62. require_once(APPROOT.'/application/clipage.class.inc.php');
  63. require_once(APPROOT.'/application/startup.inc.php');
  64. class RestServices
  65. {
  66. public function InitTrackingComment($oData)
  67. {
  68. $sComment = $this->GetMandatoryParam($oData, 'comment');
  69. CMDBObject::SetTrackInfo($sComment);
  70. }
  71. public function GetMandatoryParam($oData, $sParamName)
  72. {
  73. if (isset($oData->$sParamName))
  74. {
  75. return $oData->$sParamName;
  76. }
  77. else
  78. {
  79. throw new Exception("Missing parameter '$sParamName'");
  80. }
  81. }
  82. public function GetOptionalParam($oData, $sParamName, $default)
  83. {
  84. if (isset($oData->$sParamName))
  85. {
  86. return $oData->$sParamName;
  87. }
  88. else
  89. {
  90. return $default;
  91. }
  92. }
  93. public function GetClass($oData, $sParamName)
  94. {
  95. $sClass = $this->GetMandatoryParam($oData, $sParamName);
  96. if (!MetaModel::IsValidClass($sClass))
  97. {
  98. throw new Exception("$sParamName: '$sClass' is not a valid class'");
  99. }
  100. return $sClass;
  101. }
  102. public function GetFieldList($sClass, $oData, $sParamName)
  103. {
  104. $sFields = $this->GetOptionalParam($oData, $sParamName, '*');
  105. $aShowFields = array();
  106. if ($sFields == '*')
  107. {
  108. foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  109. {
  110. $aShowFields[] = $sAttCode;
  111. }
  112. }
  113. else
  114. {
  115. foreach(explode(',', $sFields) as $sAttCode)
  116. {
  117. $sAttCode = trim($sAttCode);
  118. if (($sAttCode != 'id') && (!MetaModel::IsValidAttCode($sClass, $sAttCode)))
  119. {
  120. throw new Exception("$sParamName: invalid attribute code '$sAttCode'");
  121. }
  122. $aShowFields[] = $sAttCode;
  123. }
  124. }
  125. return $aShowFields;
  126. }
  127. protected function FindObjectFromCriteria($sClass, $oCriteria)
  128. {
  129. $aCriteriaReport = array();
  130. if (isset($oCriteria->finalclass))
  131. {
  132. $sClass = $oCriteria->finalclass;
  133. if (!MetaModel::IsValidClass($sClass))
  134. {
  135. throw new Exception("finalclass: Unknown class '$sClass'");
  136. }
  137. }
  138. $oSearch = new DBObjectSearch($sClass);
  139. foreach ($oCriteria as $sAttCode => $value)
  140. {
  141. $realValue = $this->MakeValue($sClass, $sAttCode, $value);
  142. $oSearch->AddCondition($sAttCode, $realValue);
  143. $aCriteriaReport[] = "$sAttCode: $value ($realValue)";
  144. }
  145. $oSet = new DBObjectSet($oSearch);
  146. $iCount = $oSet->Count();
  147. if ($iCount == 0)
  148. {
  149. throw new Exception("No item found for criteria: ".implode(', ', $aCriteriaReport));
  150. }
  151. elseif ($iCount > 1)
  152. {
  153. throw new Exception("Several items found ($iCount) for criteria: ".implode(', ', $aCriteriaReport));
  154. }
  155. $res = $oSet->Fetch();
  156. return $res;
  157. }
  158. public function FindObjectFromKey($sClass, $key)
  159. {
  160. if (is_object($key))
  161. {
  162. $res = $this->FindObjectFromCriteria($sClass, $key);
  163. }
  164. elseif (is_numeric($key))
  165. {
  166. $res = MetaModel::GetObject($sClass, $key);
  167. }
  168. elseif (is_string($key))
  169. {
  170. // OQL
  171. $oSearch = DBObjectSearch::FromOQL($key);
  172. $oSet = new DBObjectSet($oSearch);
  173. $iCount = $oSet->Count();
  174. if ($iCount == 0)
  175. {
  176. throw new Exception("No item found for query: $key");
  177. }
  178. elseif ($iCount > 1)
  179. {
  180. throw new Exception("Several items found ($iCount) for query: $key");
  181. }
  182. $res = $oSet->Fetch();
  183. }
  184. else
  185. {
  186. throw new Exception("Wrong format for key");
  187. }
  188. return $res;
  189. }
  190. public function GetObjectSetFromKey($sClass, $key)
  191. {
  192. if (is_object($key))
  193. {
  194. if (isset($oCriteria->finalclass))
  195. {
  196. $sClass = $oCriteria->finalclass;
  197. if (!MetaModel::IsValidClass($sClass))
  198. {
  199. throw new Exception("finalclass: Unknown class '$sClass'");
  200. }
  201. }
  202. $oSearch = new DBObjectSearch($sClass);
  203. foreach ($key as $sAttCode => $value)
  204. {
  205. $realValue = $this->MakeValue($sClass, $sAttCode, $value);
  206. $oSearch->AddCondition($sAttCode, $realValue);
  207. }
  208. }
  209. elseif (is_numeric($key))
  210. {
  211. $oSearch = new DBObjectSearch($sClass);
  212. $oSearch->AddCondition('id', $key);
  213. }
  214. elseif (is_string($key))
  215. {
  216. // OQL
  217. $oSearch = DBObjectSearch::FromOQL($key);
  218. $oObjectSet = new DBObjectSet($oSearch);
  219. }
  220. else
  221. {
  222. throw new Exception("Wrong format for key");
  223. }
  224. $oObjectSet = new DBObjectSet($oSearch);
  225. return $oObjectSet;
  226. }
  227. protected function MakeValue($sClass, $sAttCode, $value)
  228. {
  229. try
  230. {
  231. if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
  232. {
  233. throw new Exception("Unknown attribute");
  234. }
  235. $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  236. if ($oAttDef instanceof AttributeExternalKey)
  237. {
  238. $oExtKeyObject = $this->FindObjectFromKey($oAttDef->GetTargetClass(), $value);
  239. $value = $oExtKeyObject->GetKey();
  240. }
  241. elseif ($oAttDef instanceof AttributeLinkedSet)
  242. {
  243. if (!is_array($value))
  244. {
  245. throw new Exception("A link set must be defined by an array of objects");
  246. }
  247. $sLnkClass = $oAttDef->GetLinkedClass();
  248. $aLinks = array();
  249. foreach($value as $oValues)
  250. {
  251. $oLnk = $this->MakeObjectFromFields($sLnkClass, $oValues);
  252. $aLinks[] = $oLnk;
  253. }
  254. $value = DBObjectSet::FromArray($sLnkClass, $aLinks);
  255. }
  256. }
  257. catch (Exception $e)
  258. {
  259. throw new Exception("$sAttCode: ".$e->getMessage());
  260. }
  261. return $value;
  262. }
  263. public function MakeObjectFromFields($sClass, $aFields)
  264. {
  265. $oObject = MetaModel::NewObject($sClass);
  266. foreach ($aFields as $sAttCode => $value)
  267. {
  268. $realValue = $this->MakeValue($sClass, $sAttCode, $value);
  269. $oObject->Set($sAttCode, $realValue);
  270. }
  271. return $oObject;
  272. }
  273. public function UpdateObjectFromFields($oObject, $aFields)
  274. {
  275. $sClass = get_class($oObject);
  276. foreach ($aFields as $sAttCode => $value)
  277. {
  278. $realValue = $this->MakeValue($sClass, $sAttCode, $value);
  279. $oObject->Set($sAttCode, $realValue);
  280. }
  281. return $oObject;
  282. }
  283. }
  284. class FieldResult
  285. {
  286. protected $value;
  287. public function __construct()
  288. {
  289. }
  290. public function GetValue()
  291. {
  292. }
  293. }
  294. class ObjectResult
  295. {
  296. public $code;
  297. public $message;
  298. public $fields;
  299. public function __construct()
  300. {
  301. $this->code = 0;
  302. $this->message = '';
  303. $this->fields = array();
  304. }
  305. protected function MakeResultValue($oObject, $sAttCode)
  306. {
  307. if ($sAttCode == 'id')
  308. {
  309. $value = $oObject->GetKey();
  310. }
  311. else
  312. {
  313. $sClass = get_class($oObject);
  314. $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  315. if ($oAttDef instanceof AttributeLinkedSet)
  316. {
  317. $value = array();
  318. // Make the list of required attributes
  319. // - Skip attributes pointing to the current object (redundant data)
  320. // - Skip link sets refering to the current data (infinite recursion!)
  321. $aRelevantAttributes = array();
  322. $sLnkClass = $oAttDef->GetLinkedClass();
  323. foreach (MetaModel::ListAttributeDefs($sLnkClass) as $sLnkAttCode => $oLnkAttDef)
  324. {
  325. // Skip any attribute of the link that points to the current object
  326. //
  327. if ($sLnkAttCode == $oAttDef->GetExtKeyToMe()) continue;
  328. if (method_exists($oLnkAttDef, 'GetKeyAttCode'))
  329. {
  330. if ($oLnkAttDef->GetKeyAttCode() ==$oAttDef->GetExtKeyToMe()) continue;
  331. }
  332. $aRelevantAttributes[] = $sLnkAttCode;
  333. }
  334. // Iterate on the set and build an array of array of attcode=>value
  335. $oSet = $oObject->Get($sAttCode);
  336. while ($oLnk = $oSet->Fetch())
  337. {
  338. $aLnkValues = array();
  339. foreach ($aRelevantAttributes as $sLnkAttCode)
  340. {
  341. $aLnkValues[$sLnkAttCode] = $this->MakeResultValue($oLnk, $sLnkAttCode);
  342. }
  343. $value[] = $aLnkValues;
  344. }
  345. }
  346. else
  347. {
  348. $value = $oObject->GetEditValue($sAttCode);
  349. }
  350. }
  351. return $value;
  352. }
  353. public function AddField($oObject, $sAttCode)
  354. {
  355. $this->fields[$sAttCode] = $this->MakeResultValue($oObject, $sAttCode);
  356. }
  357. }
  358. class RestResult
  359. {
  360. public function __construct()
  361. {
  362. }
  363. public $code;
  364. public $message;
  365. public $objects;
  366. public function AddObject($iCode, $sMessage, $oObject = null, $aFields = null)
  367. {
  368. $oObjRes = new ObjectResult();
  369. $oObjRes->code = $iCode;
  370. $oObjRes->message = $sMessage;
  371. if ($oObject)
  372. {
  373. foreach ($aFields as $sAttCode)
  374. {
  375. $oObjRes->AddField($oObject, $sAttCode);
  376. }
  377. }
  378. $this->objects[] = $oObjRes;
  379. }
  380. }
  381. ////////////////////////////////////////////////////////////////////////////////
  382. //
  383. // Main
  384. //
  385. $oP = new CLIPage("iTop - REST");
  386. $oResult = new RestResult();
  387. try
  388. {
  389. utils::UseParamFile();
  390. $sAuthUser = utils::ReadPostedParam('auth_user', null, 'raw_data');
  391. $sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
  392. if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd))
  393. {
  394. UserRights::Login($sAuthUser); // Login & set the user's language
  395. }
  396. else
  397. {
  398. throw new Exception("Invalid login '$sAuthUser'");
  399. }
  400. $aJsonData = json_decode(utils::ReadPostedParam('json_data', null, 'raw_data'));
  401. if ($aJsonData == null)
  402. {
  403. throw new Exception('Parameter json_data is not a valid JSON structure');
  404. }
  405. $oRS = new RestServices();
  406. $sOperation = $oRS->GetMandatoryParam($aJsonData, 'operation');
  407. switch ($sOperation)
  408. {
  409. case 'object_create':
  410. $oRS->InitTrackingComment($aJsonData);
  411. $sClass = $oRS->GetClass($aJsonData, 'class');
  412. $aFields = $oRS->GetMandatoryParam($aJsonData, 'fields');
  413. $aShowFields = $oRS->GetFieldList($sClass, $aJsonData, 'results');
  414. $oObject = $oRS->MakeObjectFromFields($sClass, $aFields);
  415. $oObject->DBInsert();
  416. $oResult->AddObject(0, 'created', $oObject, $aShowFields);
  417. break;
  418. case 'object_update':
  419. $oRS->InitTrackingComment($aJsonData);
  420. $sClass = $oRS->GetClass($aJsonData, 'class');
  421. $key = $oRS->GetMandatoryParam($aJsonData, 'key');
  422. $aFields = $oRS->GetMandatoryParam($aJsonData, 'fields');
  423. $aShowFields = $oRS->GetFieldList($sClass, $aJsonData, 'results');
  424. $oObject = $oRS->FindObjectFromKey($sClass, $key);
  425. $oRS->UpdateObjectFromFields($oObject, $aFields);
  426. $oObject->DBUpdate();
  427. $oResult->AddObject(0, 'updated', $oObject, $aShowFields);
  428. break;
  429. case 'object_get':
  430. $sClass = $oRS->GetClass($aJsonData, 'class');
  431. $key = $oRS->GetMandatoryParam($aJsonData, 'key');
  432. $aShowFields = $oRS->GetFieldList($sClass, $aJsonData, 'results');
  433. $oObjectSet = $oRS->GetObjectSetFromKey($sClass, $key);
  434. while ($oObject = $oObjectSet->Fetch())
  435. {
  436. $oResult->AddObject(0, '', $oObject, $aShowFields);
  437. }
  438. $oResult->message = "Found: ".$oObjectSet->Count();
  439. break;
  440. default:
  441. throw new Exception("Uknown operation '$sOperation'");
  442. }
  443. }
  444. catch(Exception $e)
  445. {
  446. $oResult->code = 1234;
  447. $oResult->message = "Error: ".$e->GetMessage();
  448. }
  449. $oP->add(json_encode($oResult));
  450. $oP->Output();
  451. ?>