restservices.class.inc.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  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 $class;
  38. public $key;
  39. public $fields;
  40. /**
  41. * Default constructor
  42. */
  43. public function __construct($sClass = null, $iId = null)
  44. {
  45. $this->code = RestResult::OK;
  46. $this->message = '';
  47. $this->class = $sClass;
  48. $this->key = $iId;
  49. $this->fields = array();
  50. }
  51. /**
  52. * Helper to make an output value for a given attribute
  53. *
  54. * @param DBObject $oObject The object being reported
  55. * @param string $sAttCode The attribute code (must be valid)
  56. * @param boolean $bExtendedOutput Output all of the link set attributes ?
  57. * @return string A scalar representation of the value
  58. */
  59. protected function MakeResultValue(DBObject $oObject, $sAttCode, $bExtendedOutput = false)
  60. {
  61. if ($sAttCode == 'id')
  62. {
  63. $value = $oObject->GetKey();
  64. }
  65. else
  66. {
  67. $sClass = get_class($oObject);
  68. $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  69. if ($oAttDef instanceof AttributeLinkedSet)
  70. {
  71. // Iterate on the set and build an array of array of attcode=>value
  72. $oSet = $oObject->Get($sAttCode);
  73. $value = array();
  74. while ($oLnk = $oSet->Fetch())
  75. {
  76. $sLnkRefClass = $bExtendedOutput ? get_class($oLnk) : $oAttDef->GetLinkedClass();
  77. $aLnkValues = array();
  78. foreach (MetaModel::ListAttributeDefs($sLnkRefClass) as $sLnkAttCode => $oLnkAttDef)
  79. {
  80. // Skip attributes pointing to the current object (redundant data)
  81. if ($sLnkAttCode == $oAttDef->GetExtKeyToMe())
  82. {
  83. continue;
  84. }
  85. // Skip any attribute of the link that points to the current object
  86. $oLnkAttDef = MetaModel::GetAttributeDef($sLnkRefClass, $sLnkAttCode);
  87. if (method_exists($oLnkAttDef, 'GetKeyAttCode'))
  88. {
  89. if ($oLnkAttDef->GetKeyAttCode() == $oAttDef->GetExtKeyToMe())
  90. {
  91. continue;
  92. }
  93. }
  94. $aLnkValues[$sLnkAttCode] = $this->MakeResultValue($oLnk, $sLnkAttCode, $bExtendedOutput);
  95. }
  96. $value[] = $aLnkValues;
  97. }
  98. }
  99. else
  100. {
  101. $value = $oAttDef->GetForJSON($oObject->Get($sAttCode));
  102. }
  103. }
  104. return $value;
  105. }
  106. /**
  107. * Report the value for the given object attribute
  108. *
  109. * @param DBObject $oObject The object being reported
  110. * @param string $sAttCode The attribute code (must be valid)
  111. * @param boolean $bExtendedOutput Output all of the link set attributes ?
  112. * @return void
  113. */
  114. public function AddField(DBObject $oObject, $sAttCode, $bExtendedOutput = false)
  115. {
  116. $this->fields[$sAttCode] = $this->MakeResultValue($oObject, $sAttCode, $bExtendedOutput);
  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 $aFieldSpec An array of class => attribute codes (Cf. RestUtils::GetFieldList). List of the attributes to be reported.
  136. * @param boolean $bExtendedOutput Output all of the link set attributes ?
  137. * @return void
  138. */
  139. public function AddObject($iCode, $sMessage, $oObject, $aFieldSpec = null, $bExtendedOutput = false)
  140. {
  141. $sClass = get_class($oObject);
  142. $oObjRes = new ObjectResult($sClass, $oObject->GetKey());
  143. $oObjRes->code = $iCode;
  144. $oObjRes->message = $sMessage;
  145. $aFields = null;
  146. if (!is_null($aFieldSpec))
  147. {
  148. // Enum all classes in the hierarchy, starting with the current one
  149. foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sRefClass)
  150. {
  151. if (array_key_exists($sRefClass, $aFieldSpec))
  152. {
  153. $aFields = $aFieldSpec[$sRefClass];
  154. break;
  155. }
  156. }
  157. }
  158. if (is_null($aFields))
  159. {
  160. // No fieldspec given, or not found...
  161. $aFields = array('id', 'friendlyname');
  162. }
  163. foreach ($aFields as $sAttCode)
  164. {
  165. $oObjRes->AddField($oObject, $sAttCode, $bExtendedOutput);
  166. }
  167. $sObjKey = get_class($oObject).'::'.$oObject->GetKey();
  168. $this->objects[$sObjKey] = $oObjRes;
  169. }
  170. }
  171. class RestResultWithRelations extends RestResultWithObjects
  172. {
  173. public $relations;
  174. public function __construct()
  175. {
  176. parent::__construct();
  177. $this->relations = array();
  178. }
  179. public function AddRelation($sSrcKey, $sDestKey)
  180. {
  181. if (!array_key_exists($sSrcKey, $this->relations))
  182. {
  183. $this->relations[$sSrcKey] = array();
  184. }
  185. $this->relations[$sSrcKey][] = array('key' => $sDestKey);
  186. }
  187. }
  188. /**
  189. * Deletion result codes for a target object (either deleted or updated)
  190. *
  191. * @package Extensibility
  192. * @api
  193. * @since 2.0.1
  194. */
  195. class RestDelete
  196. {
  197. /**
  198. * Result: Object deleted as per the initial request
  199. */
  200. const OK = 0;
  201. /**
  202. * Result: general issue (user rights or ... ?)
  203. */
  204. const ISSUE = 1;
  205. /**
  206. * Result: Must be deleted to preserve database integrity
  207. */
  208. const AUTO_DELETE = 2;
  209. /**
  210. * Result: Must be deleted to preserve database integrity, but that is NOT possible
  211. */
  212. const AUTO_DELETE_ISSUE = 3;
  213. /**
  214. * Result: Must be deleted to preserve database integrity, but this must be requested explicitely
  215. */
  216. const REQUEST_EXPLICITELY = 4;
  217. /**
  218. * Result: Must be updated to preserve database integrity
  219. */
  220. const AUTO_UPDATE = 5;
  221. /**
  222. * Result: Must be updated to preserve database integrity, but that is NOT possible
  223. */
  224. const AUTO_UPDATE_ISSUE = 6;
  225. }
  226. /**
  227. * Implementation of core REST services (create/get/update... objects)
  228. *
  229. * @package Core
  230. */
  231. class CoreServices implements iRestServiceProvider
  232. {
  233. /**
  234. * Enumerate services delivered by this class
  235. *
  236. * @param string $sVersion The version (e.g. 1.0) supported by the services
  237. * @return array An array of hash 'verb' => verb, 'description' => description
  238. */
  239. public function ListOperations($sVersion)
  240. {
  241. // 1.1 - In the reply, objects have a 'key' entry so that it is no more necessary to split class::key programmaticaly
  242. //
  243. $aOps = array();
  244. if (in_array($sVersion, array('1.0', '1.1')))
  245. {
  246. $aOps[] = array(
  247. 'verb' => 'core/create',
  248. 'description' => 'Create an object'
  249. );
  250. $aOps[] = array(
  251. 'verb' => 'core/update',
  252. 'description' => 'Update an object'
  253. );
  254. $aOps[] = array(
  255. 'verb' => 'core/apply_stimulus',
  256. 'description' => 'Apply a stimulus to change the state of an object'
  257. );
  258. $aOps[] = array(
  259. 'verb' => 'core/get',
  260. 'description' => 'Search for objects'
  261. );
  262. $aOps[] = array(
  263. 'verb' => 'core/delete',
  264. 'description' => 'Delete objects'
  265. );
  266. $aOps[] = array(
  267. 'verb' => 'core/get_related',
  268. 'description' => 'Get related objects through the specified relation'
  269. );
  270. $aOps[] = array(
  271. 'verb' => 'core/check_credentials',
  272. 'description' => 'Check user credentials'
  273. );
  274. }
  275. return $aOps;
  276. }
  277. /**
  278. * Enumerate services delivered by this class
  279. * @param string $sVersion The version (e.g. 1.0) supported by the services
  280. * @return RestResult The standardized result structure (at least a message)
  281. * @throws Exception in case of internal failure.
  282. */
  283. public function ExecOperation($sVersion, $sVerb, $aParams)
  284. {
  285. $oResult = new RestResultWithObjects();
  286. switch ($sVerb)
  287. {
  288. case 'core/create':
  289. RestUtils::InitTrackingComment($aParams);
  290. $sClass = RestUtils::GetClass($aParams, 'class');
  291. $aFields = RestUtils::GetMandatoryParam($aParams, 'fields');
  292. $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
  293. $bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+');
  294. $oObject = RestUtils::MakeObjectFromFields($sClass, $aFields);
  295. $oObject->DBInsert();
  296. $oResult->AddObject(0, 'created', $oObject, $aShowFields, $bExtendedOutput);
  297. break;
  298. case 'core/update':
  299. RestUtils::InitTrackingComment($aParams);
  300. $sClass = RestUtils::GetClass($aParams, 'class');
  301. $key = RestUtils::GetMandatoryParam($aParams, 'key');
  302. $aFields = RestUtils::GetMandatoryParam($aParams, 'fields');
  303. $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
  304. $bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+');
  305. $oObject = RestUtils::FindObjectFromKey($sClass, $key);
  306. RestUtils::UpdateObjectFromFields($oObject, $aFields);
  307. $oObject->DBUpdate();
  308. $oResult->AddObject(0, 'updated', $oObject, $aShowFields, $bExtendedOutput);
  309. break;
  310. case 'core/apply_stimulus':
  311. RestUtils::InitTrackingComment($aParams);
  312. $sClass = RestUtils::GetClass($aParams, 'class');
  313. $key = RestUtils::GetMandatoryParam($aParams, 'key');
  314. $aFields = RestUtils::GetMandatoryParam($aParams, 'fields');
  315. $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
  316. $bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+');
  317. $sStimulus = RestUtils::GetMandatoryParam($aParams, 'stimulus');
  318. $oObject = RestUtils::FindObjectFromKey($sClass, $key);
  319. RestUtils::UpdateObjectFromFields($oObject, $aFields);
  320. $aTransitions = $oObject->EnumTransitions();
  321. $aStimuli = MetaModel::EnumStimuli(get_class($oObject));
  322. if (!isset($aTransitions[$sStimulus]))
  323. {
  324. // Invalid stimulus
  325. $oResult->code = RestResult::INTERNAL_ERROR;
  326. $oResult->message = "Invalid stimulus: '$sStimulus' on the object ".$oObject->GetName()." in state '".$oObject->GetState()."'";
  327. }
  328. else
  329. {
  330. $aTransition = $aTransitions[$sStimulus];
  331. $sTargetState = $aTransition['target_state'];
  332. $aStates = MetaModel::EnumStates($sClass);
  333. $aTargetStateDef = $aStates[$sTargetState];
  334. $aExpectedAttributes = $aTargetStateDef['attribute_list'];
  335. $aMissingMandatory = array();
  336. foreach($aExpectedAttributes as $sAttCode => $iExpectCode)
  337. {
  338. if ( ($iExpectCode & OPT_ATT_MANDATORY) && ($oObject->Get($sAttCode) == ''))
  339. {
  340. $aMissingMandatory[] = $sAttCode;
  341. }
  342. }
  343. if (count($aMissingMandatory) == 0)
  344. {
  345. // If all the mandatory fields are already present, just apply the transition silently...
  346. if ($oObject->ApplyStimulus($sStimulus))
  347. {
  348. $oObject->DBUpdate();
  349. $oResult->AddObject(0, 'updated', $oObject, $aShowFields, $bExtendedOutput);
  350. }
  351. }
  352. else
  353. {
  354. // Missing mandatory attributes for the transition
  355. $oResult->code = RestResult::INTERNAL_ERROR;
  356. $oResult->message = 'Missing mandatory attribute(s) for applying the stimulus: '.implode(', ', $aMissingMandatory).'.';
  357. }
  358. }
  359. break;
  360. case 'core/get':
  361. $sClass = RestUtils::GetClass($aParams, 'class');
  362. $key = RestUtils::GetMandatoryParam($aParams, 'key');
  363. $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
  364. $bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+');
  365. $oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key);
  366. while ($oObject = $oObjectSet->Fetch())
  367. {
  368. $oResult->AddObject(0, '', $oObject, $aShowFields, $bExtendedOutput);
  369. }
  370. $oResult->message = "Found: ".$oObjectSet->Count();
  371. break;
  372. case 'core/delete':
  373. $sClass = RestUtils::GetClass($aParams, 'class');
  374. $key = RestUtils::GetMandatoryParam($aParams, 'key');
  375. $bSimulate = RestUtils::GetOptionalParam($aParams, 'simulate', false);
  376. $oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key);
  377. $aObjects = $oObjectSet->ToArray();
  378. $this->DeleteObjects($oResult, $aObjects, $bSimulate);
  379. break;
  380. case 'core/get_related':
  381. $oResult = new RestResultWithRelations();
  382. $sClass = RestUtils::GetClass($aParams, 'class');
  383. $key = RestUtils::GetMandatoryParam($aParams, 'key');
  384. $sRelation = RestUtils::GetMandatoryParam($aParams, 'relation');
  385. $iMaxRecursionDepth = RestUtils::GetOptionalParam($aParams, 'depth', 20 /* = MAX_RECURSION_DEPTH */);
  386. $sDirection = RestUtils::GetOptionalParam($aParams, 'direction', null);
  387. $bEnableRedundancy = RestUtils::GetOptionalParam($aParams, 'redundancy', false);
  388. $bReverse = false;
  389. if (is_null($sDirection) && ($sRelation == 'depends on'))
  390. {
  391. // Legacy behavior, consider "depends on" as a forward relation
  392. $sRelation = 'impacts';
  393. $sDirection = 'up';
  394. $bReverse = true; // emulate the legacy behavior by returning the edges
  395. }
  396. else if(is_null($sDirection))
  397. {
  398. $sDirection = 'down';
  399. }
  400. $oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key);
  401. if ($sDirection == 'down')
  402. {
  403. $oRelationGraph = $oObjectSet->GetRelatedObjectsDown($sRelation, $iMaxRecursionDepth, $bEnableRedundancy);
  404. }
  405. else if ($sDirection == 'up')
  406. {
  407. $oRelationGraph = $oObjectSet->GetRelatedObjectsUp($sRelation, $iMaxRecursionDepth, $bEnableRedundancy);
  408. }
  409. else
  410. {
  411. $oResult->code = RestResult::INTERNAL_ERROR;
  412. $oResult->message = "Invalid value: '$sDirection' for the parameter 'direction'. Valid values are 'up' and 'down'";
  413. return $oResult;
  414. }
  415. if ($bEnableRedundancy)
  416. {
  417. // Remove the redundancy nodes from the output
  418. $oIterator = new RelationTypeIterator($oRelationGraph, 'Node');
  419. foreach($oIterator as $oNode)
  420. {
  421. if ($oNode instanceof RelationRedundancyNode)
  422. {
  423. $oRelationGraph->FilterNode($oNode);
  424. }
  425. }
  426. }
  427. $aIndexByClass = array();
  428. $oIterator = new RelationTypeIterator($oRelationGraph);
  429. foreach($oIterator as $oElement)
  430. {
  431. if ($oElement instanceof RelationObjectNode)
  432. {
  433. $oObject = $oElement->GetProperty('object');
  434. if ($oObject)
  435. {
  436. if ($bEnableRedundancy)
  437. {
  438. // Add only the "reached" objects
  439. if ($oElement->GetProperty('is_reached'))
  440. {
  441. $aIndexByClass[get_class($oObject)][$oObject->GetKey()] = null;
  442. $oResult->AddObject(0, '', $oObject);
  443. }
  444. }
  445. else
  446. {
  447. $aIndexByClass[get_class($oObject)][$oObject->GetKey()] = null;
  448. $oResult->AddObject(0, '', $oObject);
  449. }
  450. }
  451. }
  452. else if ($oElement instanceof RelationEdge)
  453. {
  454. $oSrcObj = $oElement->GetSourceNode()->GetProperty('object');
  455. $oDestObj = $oElement->GetSinkNode()->GetProperty('object');
  456. $sSrcKey = get_class($oSrcObj).'::'.$oSrcObj->GetKey();
  457. $sDestKey = get_class($oDestObj).'::'.$oDestObj->GetKey();
  458. if ($bEnableRedundancy)
  459. {
  460. // Add only the edges where both source and destination are "reached"
  461. if ($oElement->GetSourceNode()->GetProperty('is_reached') && $oElement->GetSinkNode()->GetProperty('is_reached'))
  462. {
  463. if ($bReverse)
  464. {
  465. $oResult->AddRelation($sDestKey, $sSrcKey);
  466. }
  467. else
  468. {
  469. $oResult->AddRelation($sSrcKey, $sDestKey);
  470. }
  471. }
  472. }
  473. else
  474. {
  475. if ($bReverse)
  476. {
  477. $oResult->AddRelation($sDestKey, $sSrcKey);
  478. }
  479. else
  480. {
  481. $oResult->AddRelation($sSrcKey, $sDestKey);
  482. }
  483. }
  484. }
  485. }
  486. if (count($aIndexByClass) > 0)
  487. {
  488. $aStats = array();
  489. foreach ($aIndexByClass as $sClass => $aIds)
  490. {
  491. $aStats[] = $sClass.'= '.count($aIds);
  492. }
  493. $oResult->message = "Scope: ".$oObjectSet->Count()."; Related objects: ".implode(', ', $aStats);
  494. }
  495. else
  496. {
  497. $oResult->message = "Nothing found";
  498. }
  499. break;
  500. case 'core/check_credentials':
  501. $oResult = new RestResult();
  502. $sUser = RestUtils::GetMandatoryParam($aParams, 'user');
  503. $sPassword = RestUtils::GetMandatoryParam($aParams, 'password');
  504. if (UserRights::CheckCredentials($sUser, $sPassword) !== true)
  505. {
  506. $oResult->authorized = false;
  507. }
  508. else
  509. {
  510. $oResult->authorized = true;
  511. }
  512. break;
  513. default:
  514. // unknown operation: handled at a higher level
  515. }
  516. return $oResult;
  517. }
  518. /**
  519. * Helper for object deletion
  520. */
  521. public function DeleteObjects($oResult, $aObjects, $bSimulate)
  522. {
  523. $oDeletionPlan = new DeletionPlan();
  524. foreach($aObjects as $oObj)
  525. {
  526. if ($bSimulate)
  527. {
  528. $oObj->CheckToDelete($oDeletionPlan);
  529. }
  530. else
  531. {
  532. $oObj->DBDelete($oDeletionPlan);
  533. }
  534. }
  535. foreach ($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes)
  536. {
  537. foreach ($aDeletes as $iId => $aData)
  538. {
  539. $oToDelete = $aData['to_delete'];
  540. $bAutoDel = (($aData['mode'] == DEL_SILENT) || ($aData['mode'] == DEL_AUTO));
  541. if (array_key_exists('issue', $aData))
  542. {
  543. if ($bAutoDel)
  544. {
  545. if (isset($aData['requested_explicitely'])) // i.e. in the initial list of objects to delete
  546. {
  547. $iCode = RestDelete::ISSUE;
  548. $sPlanned = 'Cannot be deleted: '.$aData['issue'];
  549. }
  550. else
  551. {
  552. $iCode = RestDelete::AUTO_DELETE_ISSUE;
  553. $sPlanned = 'Should be deleted automatically... but: '.$aData['issue'];
  554. }
  555. }
  556. else
  557. {
  558. $iCode = RestDelete::REQUEST_EXPLICITELY;
  559. $sPlanned = 'Must be deleted explicitely... but: '.$aData['issue'];
  560. }
  561. }
  562. else
  563. {
  564. if ($bAutoDel)
  565. {
  566. if (isset($aData['requested_explicitely']))
  567. {
  568. $iCode = RestDelete::OK;
  569. $sPlanned = '';
  570. }
  571. else
  572. {
  573. $iCode = RestDelete::AUTO_DELETE;
  574. $sPlanned = 'Deleted automatically';
  575. }
  576. }
  577. else
  578. {
  579. $iCode = RestDelete::REQUEST_EXPLICITELY;
  580. $sPlanned = 'Must be deleted explicitely';
  581. }
  582. }
  583. $oResult->AddObject($iCode, $sPlanned, $oToDelete);
  584. }
  585. }
  586. foreach ($oDeletionPlan->ListUpdates() as $sRemoteClass => $aToUpdate)
  587. {
  588. foreach ($aToUpdate as $iId => $aData)
  589. {
  590. $oToUpdate = $aData['to_reset'];
  591. if (array_key_exists('issue', $aData))
  592. {
  593. $iCode = RestDelete::AUTO_UPDATE_ISSUE;
  594. $sPlanned = 'Should be updated automatically... but: '.$aData['issue'];
  595. }
  596. else
  597. {
  598. $iCode = RestDelete::AUTO_UPDATE;
  599. $sPlanned = 'Reset external keys: '.$aData['attributes_list'];
  600. }
  601. $oResult->AddObject($iCode, $sPlanned, $oToUpdate);
  602. }
  603. }
  604. if ($oDeletionPlan->FoundStopper())
  605. {
  606. if ($oDeletionPlan->FoundSecurityIssue())
  607. {
  608. $iRes = RestResult::UNAUTHORIZED;
  609. $sRes = 'Deletion not allowed on some objects';
  610. }
  611. elseif ($oDeletionPlan->FoundManualOperation())
  612. {
  613. $iRes = RestResult::UNSAFE;
  614. $sRes = 'The deletion requires that other objects be deleted/updated, and those operations must be requested explicitely';
  615. }
  616. else
  617. {
  618. $iRes = RestResult::INTERNAL_ERROR;
  619. $sRes = 'Some issues have been encountered. See the list of planned changes for more information about the issue(s).';
  620. }
  621. }
  622. else
  623. {
  624. $iRes = RestResult::OK;
  625. $sRes = 'Deleted: '.count($aObjects);
  626. $iIndirect = $oDeletionPlan->GetTargetCount() - count($aObjects);
  627. if ($iIndirect > 0)
  628. {
  629. $sRes .= ' plus (for DB integrity) '.$iIndirect;
  630. }
  631. }
  632. $oResult->code = $iRes;
  633. if ($bSimulate)
  634. {
  635. $oResult->message = 'SIMULATING: '.$sRes;
  636. }
  637. else
  638. {
  639. $oResult->message = $sRes;
  640. }
  641. }
  642. }