From 3cf398618e488f86afdbfaed48b3c45447058fbf Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 13 Mar 2013 11:01:16 +0000 Subject: [PATCH] #472 REST Services: added core/delete (to bulk delete, full-featured), and validated the operation core/apply_stimulus SVN:trunk[2616] --- application/applicationextension.inc.php | 10 +- core/deletionplan.class.inc.php | 3 + core/restservices.class.inc.php | 182 ++++++++++++++++++++++- webservices/itoprest.examples.php | 20 +++ 4 files changed, 213 insertions(+), 2 deletions(-) diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index f33099179..7e2a6f544 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -587,6 +587,10 @@ class RestResult * Result: the requested operation is not valid for the specified version */ const UNKNOWN_OPERATION = 11; + /** + * Result: the requested operation cannot be performed because it can cause data (integrity) loss + */ + const UNSAFE = 12; /** * Result: the operation could not be performed, see the message for troubleshooting */ @@ -789,7 +793,11 @@ class RestUtils } elseif (is_numeric($key)) { - $res = MetaModel::GetObject($sClass, $key); + $res = MetaModel::GetObject($sClass, $key, false); + if (is_null($res)) + { + throw new Exception("Invalid object $sClass::$key"); + } } elseif (is_string($key)) { diff --git a/core/deletionplan.class.inc.php b/core/deletionplan.class.inc.php index 18d7288d7..5cd079bbd 100644 --- a/core/deletionplan.class.inc.php +++ b/core/deletionplan.class.inc.php @@ -82,6 +82,9 @@ class DeletionPlan public function ComputeResults() { + $this->m_iToDelete = 0; + $this->m_iToUpdate = 0; + foreach($this->m_aToDelete as $sClass => $aToDelete) { foreach($aToDelete as $iId => $aData) diff --git a/core/restservices.class.inc.php b/core/restservices.class.inc.php index 7a7a49a0a..3d589e13b 100644 --- a/core/restservices.class.inc.php +++ b/core/restservices.class.inc.php @@ -155,6 +155,7 @@ class RestResultWithObjects extends RestResult if ($oObject) { + $oObjRes->class = get_class($oObject); foreach ($aFields as $sAttCode) { $oObjRes->AddField($oObject, $sAttCode); @@ -165,6 +166,44 @@ class RestResultWithObjects extends RestResult } } +/** + * Deletion result codes for a target object (either deleted or updated) + * + * @package Extensibility + * @api + * @since 2.0.1 + */ +class RestDelete +{ + /** + * Result: Object deleted as per the initial request + */ + const OK = 0; + /** + * Result: general issue (user rights or ... ?) + */ + const ISSUE = 1; + /** + * Result: Must be deleted to preserve database integrity + */ + const AUTO_DELETE = 2; + /** + * Result: Must be deleted to preserve database integrity, but that is NOT possible + */ + const AUTO_DELETE_ISSUE = 3; + /** + * Result: Must be deleted to preserve database integrity, but this must be requested explicitely + */ + const REQUEST_EXPLICITELY = 4; + /** + * Result: Must be updated to preserve database integrity + */ + const AUTO_UPDATE = 5; + /** + * Result: Must be updated to preserve database integrity, but that is NOT possible + */ + const AUTO_UPDATE_ISSUE = 6; +} /** * Implementation of core REST services (create/get/update... objects) @@ -200,6 +239,10 @@ class CoreServices implements iRestServiceProvider 'verb' => 'core/get', 'description' => 'Search for objects' ); + $aOps[] = array( + 'verb' => 'core/delete', + 'description' => 'Delete objects' + ); } return $aOps; } @@ -294,7 +337,7 @@ class CoreServices implements iRestServiceProvider } break; - case 'core/get': + case 'core/get': $sClass = RestUtils::GetClass($aParams, 'class'); $key = RestUtils::GetMandatoryParam($aParams, 'key'); $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields'); @@ -306,10 +349,147 @@ class CoreServices implements iRestServiceProvider } $oResult->message = "Found: ".$oObjectSet->Count(); break; + + case 'core/delete': + $sClass = RestUtils::GetClass($aParams, 'class'); + $key = RestUtils::GetMandatoryParam($aParams, 'key'); + $bSimulate = RestUtils::GetOptionalParam($aParams, 'simulate', false); + $oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key); + $aObjects = $oObjectSet->ToArray(); + $this->DeleteObjects($oResult, $aObjects, $bSimulate); + break; + default: // unknown operation: handled at a higher level } return $oResult; } + + /** + * Helper for object deletion + */ + public function DeleteObjects($oResult, $aObjects, $bSimulate) + { + $oDeletionPlan = new DeletionPlan(); + foreach($aObjects as $oObj) + { + if ($bSimulate) + { + $oObj->CheckToDelete($oDeletionPlan); + } + else + { + $oObj->DBDelete($oDeletionPlan); + } + } + + foreach ($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes) + { + foreach ($aDeletes as $iId => $aData) + { + $oToDelete = $aData['to_delete']; + $bAutoDel = (($aData['mode'] == DEL_SILENT) || ($aData['mode'] == DEL_AUTO)); + if (array_key_exists('issue', $aData)) + { + if ($bAutoDel) + { + if (isset($aData['requested_explicitely'])) // i.e. in the initial list of objects to delete + { + $iCode = RestDelete::ISSUE; + $sPlanned = 'Cannot be deleted: '.$aData['issue']; + } + else + { + $iCode = RestDelete::AUTO_DELETE_ISSUE; + $sPlanned = 'Should be deleted automatically... but: '.$aData['issue']; + } + } + else + { + $iCode = RestDelete::REQUEST_EXPLICITELY; + $sPlanned = 'Must be deleted explicitely... but: '.$aData['issue']; + } + } + else + { + if ($bAutoDel) + { + if (isset($aData['requested_explicitely'])) + { + $iCode = RestDelete::OK; + $sPlanned = ''; + } + else + { + $iCode = RestDelete::AUTO_DELETE; + $sPlanned = 'Deleted automatically'; + } + } + else + { + $iCode = RestDelete::REQUEST_EXPLICITELY; + $sPlanned = 'Must be deleted explicitely'; + } + } + $oResult->AddObject($iCode, $sPlanned, $oToDelete, array('id', 'friendlyname')); + } + } + foreach ($oDeletionPlan->ListUpdates() as $sRemoteClass => $aToUpdate) + { + foreach ($aToUpdate as $iId => $aData) + { + $oToUpdate = $aData['to_reset']; + if (array_key_exists('issue', $aData)) + { + $iCode = RestDelete::AUTO_UPDATE_ISSUE; + $sPlanned = 'Should be updated automatically... but: '.$aData['issue']; + } + else + { + $iCode = RestDelete::AUTO_UPDATE; + $sPlanned = 'Reset external keys: '.$aData['attributes_list']; + } + $oResult->AddObject($iCode, $sPlanned, $oToUpdate, array('id', 'friendlyname')); + } + } + + if ($oDeletionPlan->FoundStopper()) + { + if ($oDeletionPlan->FoundSecurityIssue()) + { + $iRes = RestResult::UNAUTHORIZED; + $sRes = 'Deletion not allowed on some objects'; + } + elseif ($oDeletionPlan->FoundManualOperation()) + { + $iRes = RestResult::UNSAFE; + $sRes = 'The deletion requires that other objects be deleted/updated, and those operations must be requested explicitely'; + } + else + { + $iRes = RestResult::INTERNAL_ERROR; + $sRes = 'Some issues have been encountered. See the list of planned changes for more information about the issue(s).'; + } + } + else + { + $iRes = RestResult::OK; + $sRes = 'Deleted: '.count($aObjects); + $iIndirect = $oDeletionPlan->GetTargetCount() - count($aObjects); + if ($iIndirect > 0) + { + $sRes .= ' plus (for DB integrity) '.$iIndirect; + } + } + $oResult->code = $iRes; + if ($bSimulate) + { + $oResult->message = 'SIMULATING: '.$sRes; + } + else + { + $oResult->message = $sRes; + } + } } diff --git a/webservices/itoprest.examples.php b/webservices/itoprest.examples.php index f040b0c0f..733db94d5 100644 --- a/webservices/itoprest.examples.php +++ b/webservices/itoprest.examples.php @@ -130,6 +130,26 @@ $aOperations = array( 'key' => 'SELECT UserRequest', 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) ), + array( + 'operation' => 'core/delete', // operation code + 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 'SELECT UserRequest WHERE org_id = 2', + 'simulate' => true, + ), + array( + 'operation' => 'core/apply_stimulus', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 1, + 'stimulus' => 'ev_assign', + // Values to set + 'fields' => array( + 'team_id' => 15, // Helpdesk + 'agent_id' => 9 // Jules Verne + ), + 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) + ), ); $sUrl = "http://localhost/rest-services/webservices/rest.php?version=1.0";