1<?php 2// Copyright (C) 2013-2015 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/** 20 * REST/json services 21 * 22 * Definition of common structures + the very minimum service provider (manage objects) 23 * 24 * @package REST Services 25 * @copyright Copyright (C) 2013 Combodo SARL 26 * @license http://opensource.org/licenses/AGPL-3.0 27 * @api 28 */ 29 30/** 31 * Element of the response formed by RestResultWithObjects 32 * 33 * @package REST Services 34 */ 35class ObjectResult 36{ 37 public $code; 38 public $message; 39 public $class; 40 public $key; 41 public $fields; 42 43 /** 44 * Default constructor 45 */ 46 public function __construct($sClass = null, $iId = null) 47 { 48 $this->code = RestResult::OK; 49 $this->message = ''; 50 $this->class = $sClass; 51 $this->key = $iId; 52 $this->fields = array(); 53 } 54 55 /** 56 * Helper to make an output value for a given attribute 57 * 58 * @param DBObject $oObject The object being reported 59 * @param string $sAttCode The attribute code (must be valid) 60 * @param boolean $bExtendedOutput Output all of the link set attributes ? 61 * @return string A scalar representation of the value 62 */ 63 protected function MakeResultValue(DBObject $oObject, $sAttCode, $bExtendedOutput = false) 64 { 65 if ($sAttCode == 'id') 66 { 67 $value = $oObject->GetKey(); 68 } 69 else 70 { 71 $sClass = get_class($oObject); 72 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 73 if ($oAttDef instanceof AttributeLinkedSet) 74 { 75 // Iterate on the set and build an array of array of attcode=>value 76 $oSet = $oObject->Get($sAttCode); 77 $value = array(); 78 while ($oLnk = $oSet->Fetch()) 79 { 80 $sLnkRefClass = $bExtendedOutput ? get_class($oLnk) : $oAttDef->GetLinkedClass(); 81 82 $aLnkValues = array(); 83 foreach (MetaModel::ListAttributeDefs($sLnkRefClass) as $sLnkAttCode => $oLnkAttDef) 84 { 85 // Skip attributes pointing to the current object (redundant data) 86 if ($sLnkAttCode == $oAttDef->GetExtKeyToMe()) 87 { 88 continue; 89 } 90 // Skip any attribute of the link that points to the current object 91 $oLnkAttDef = MetaModel::GetAttributeDef($sLnkRefClass, $sLnkAttCode); 92 if (method_exists($oLnkAttDef, 'GetKeyAttCode')) 93 { 94 if ($oLnkAttDef->GetKeyAttCode() == $oAttDef->GetExtKeyToMe()) 95 { 96 continue; 97 } 98 } 99 100 $aLnkValues[$sLnkAttCode] = $this->MakeResultValue($oLnk, $sLnkAttCode, $bExtendedOutput); 101 } 102 $value[] = $aLnkValues; 103 } 104 } 105 else 106 { 107 $value = $oAttDef->GetForJSON($oObject->Get($sAttCode)); 108 } 109 } 110 return $value; 111 } 112 113 /** 114 * Report the value for the given object attribute 115 * 116 * @param DBObject $oObject The object being reported 117 * @param string $sAttCode The attribute code (must be valid) 118 * @param boolean $bExtendedOutput Output all of the link set attributes ? 119 * @return void 120 */ 121 public function AddField(DBObject $oObject, $sAttCode, $bExtendedOutput = false) 122 { 123 $this->fields[$sAttCode] = $this->MakeResultValue($oObject, $sAttCode, $bExtendedOutput); 124 } 125} 126 127 128 129/** 130 * REST response for services managing objects. Derive this structure to add information and/or constants 131 * 132 * @package Extensibility 133 * @package REST Services 134 * @api 135 */ 136class RestResultWithObjects extends RestResult 137{ 138 public $objects; 139 140 /** 141 * Report the given object 142 * 143 * @param int An error code (RestResult::OK is no issue has been found) 144 * @param string $sMessage Description of the error if any, an empty string otherwise 145 * @param DBObject $oObject The object being reported 146 * @param array $aFieldSpec An array of class => attribute codes (Cf. RestUtils::GetFieldList). List of the attributes to be reported. 147 * @param boolean $bExtendedOutput Output all of the link set attributes ? 148 * @return void 149 */ 150 public function AddObject($iCode, $sMessage, $oObject, $aFieldSpec = null, $bExtendedOutput = false) 151 { 152 $sClass = get_class($oObject); 153 $oObjRes = new ObjectResult($sClass, $oObject->GetKey()); 154 $oObjRes->code = $iCode; 155 $oObjRes->message = $sMessage; 156 157 $aFields = null; 158 if (!is_null($aFieldSpec)) 159 { 160 // Enum all classes in the hierarchy, starting with the current one 161 foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sRefClass) 162 { 163 if (array_key_exists($sRefClass, $aFieldSpec)) 164 { 165 $aFields = $aFieldSpec[$sRefClass]; 166 break; 167 } 168 } 169 } 170 if (is_null($aFields)) 171 { 172 // No fieldspec given, or not found... 173 $aFields = array('id', 'friendlyname'); 174 } 175 176 foreach ($aFields as $sAttCode) 177 { 178 $oObjRes->AddField($oObject, $sAttCode, $bExtendedOutput); 179 } 180 181 $sObjKey = get_class($oObject).'::'.$oObject->GetKey(); 182 $this->objects[$sObjKey] = $oObjRes; 183 } 184} 185 186class RestResultWithRelations extends RestResultWithObjects 187{ 188 public $relations; 189 190 public function __construct() 191 { 192 parent::__construct(); 193 $this->relations = array(); 194 } 195 196 public function AddRelation($sSrcKey, $sDestKey) 197 { 198 if (!array_key_exists($sSrcKey, $this->relations)) 199 { 200 $this->relations[$sSrcKey] = array(); 201 } 202 $this->relations[$sSrcKey][] = array('key' => $sDestKey); 203 } 204} 205 206/** 207 * Deletion result codes for a target object (either deleted or updated) 208 * 209 * @package Extensibility 210 * @api 211 * @since 2.0.1 212 */ 213class RestDelete 214{ 215 /** 216 * Result: Object deleted as per the initial request 217 */ 218 const OK = 0; 219 /** 220 * Result: general issue (user rights or ... ?) 221 */ 222 const ISSUE = 1; 223 /** 224 * Result: Must be deleted to preserve database integrity 225 */ 226 const AUTO_DELETE = 2; 227 /** 228 * Result: Must be deleted to preserve database integrity, but that is NOT possible 229 */ 230 const AUTO_DELETE_ISSUE = 3; 231 /** 232 * Result: Must be deleted to preserve database integrity, but this must be requested explicitely 233 */ 234 const REQUEST_EXPLICITELY = 4; 235 /** 236 * Result: Must be updated to preserve database integrity 237 */ 238 const AUTO_UPDATE = 5; 239 /** 240 * Result: Must be updated to preserve database integrity, but that is NOT possible 241 */ 242 const AUTO_UPDATE_ISSUE = 6; 243} 244 245/** 246 * Implementation of core REST services (create/get/update... objects) 247 * 248 * @package Core 249 */ 250class CoreServices implements iRestServiceProvider 251{ 252 /** 253 * Enumerate services delivered by this class 254 * 255 * @param string $sVersion The version (e.g. 1.0) supported by the services 256 * @return array An array of hash 'verb' => verb, 'description' => description 257 */ 258 public function ListOperations($sVersion) 259 { 260 // 1.4 - iTop 2.5.2, 2.6.1, 2.7.0, Verb 'core/get': added pagination parameters limit and page 261 // 1.3 - iTop 2.2.0, Verb 'get_related': added the options 'redundancy' and 'direction' to take into account the redundancy in the impact analysis 262 // 1.2 - was documented in the wiki but never released ! Same as 1.3 263 // 1.1 - In the reply, objects have a 'key' entry so that it is no more necessary to split class::key programmaticaly 264 // 1.0 - Initial implementation in iTop 2.0.1 265 // 266 $aOps = array(); 267 if (in_array($sVersion, array('1.0', '1.1', '1.2', '1.3', '1.4'))) 268 { 269 $aOps[] = array( 270 'verb' => 'core/create', 271 'description' => 'Create an object' 272 ); 273 $aOps[] = array( 274 'verb' => 'core/update', 275 'description' => 'Update an object' 276 ); 277 $aOps[] = array( 278 'verb' => 'core/apply_stimulus', 279 'description' => 'Apply a stimulus to change the state of an object' 280 ); 281 $aOps[] = array( 282 'verb' => 'core/get', 283 'description' => 'Search for objects' 284 ); 285 $aOps[] = array( 286 'verb' => 'core/delete', 287 'description' => 'Delete objects' 288 ); 289 $aOps[] = array( 290 'verb' => 'core/get_related', 291 'description' => 'Get related objects through the specified relation' 292 ); 293 $aOps[] = array( 294 'verb' => 'core/check_credentials', 295 'description' => 'Check user credentials' 296 ); 297 } 298 return $aOps; 299 } 300 301 /** 302 * Enumerate services delivered by this class 303 * 304 * @param string $sVersion The version (e.g. 1.0) supported by the services 305 * @param string $sVerb 306 * @param $aParams 307 * 308 * @return RestResult The standardized result structure (at least a message) 309 * @throws \CoreException 310 * @throws \CoreUnexpectedValue 311 * @throws \SimpleGraphException 312 * @throws \Exception 313 */ 314 public function ExecOperation($sVersion, $sVerb, $aParams) 315 { 316 $oResult = new RestResultWithObjects(); 317 switch ($sVerb) 318 { 319 case 'core/create': 320 RestUtils::InitTrackingComment($aParams); 321 $sClass = RestUtils::GetClass($aParams, 'class'); 322 $aFields = RestUtils::GetMandatoryParam($aParams, 'fields'); 323 $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields'); 324 $bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+'); 325 326 if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) != UR_ALLOWED_YES) 327 { 328 $oResult->code = RestResult::UNAUTHORIZED; 329 $oResult->message = "The current user does not have enough permissions for creating data of class $sClass"; 330 } 331 elseif (UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY) != UR_ALLOWED_YES) 332 { 333 $oResult->code = RestResult::UNAUTHORIZED; 334 $oResult->message = "The current user does not have enough permissions for massively creating data of class $sClass"; 335 } 336 else 337 { 338 $oObject = RestUtils::MakeObjectFromFields($sClass, $aFields); 339 $oObject->DBInsert(); 340 $oResult->AddObject(0, 'created', $oObject, $aShowFields, $bExtendedOutput); 341 } 342 break; 343 344 case 'core/update': 345 RestUtils::InitTrackingComment($aParams); 346 $sClass = RestUtils::GetClass($aParams, 'class'); 347 $key = RestUtils::GetMandatoryParam($aParams, 'key'); 348 $aFields = RestUtils::GetMandatoryParam($aParams, 'fields'); 349 $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields'); 350 $bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+'); 351 352 // Note: the target class cannot be based on the result of FindObjectFromKey, because in case the user does not have read access, that function already fails with msg 'Nothing found' 353 $sTargetClass = RestUtils::GetObjectSetFromKey($sClass, $key)->GetFilter()->GetClass(); 354 if (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_MODIFY) != UR_ALLOWED_YES) 355 { 356 $oResult->code = RestResult::UNAUTHORIZED; 357 $oResult->message = "The current user does not have enough permissions for modifying data of class $sTargetClass"; 358 } 359 elseif (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_MODIFY) != UR_ALLOWED_YES) 360 { 361 $oResult->code = RestResult::UNAUTHORIZED; 362 $oResult->message = "The current user does not have enough permissions for massively modifying data of class $sTargetClass"; 363 } 364 else 365 { 366 $oObject = RestUtils::FindObjectFromKey($sClass, $key); 367 RestUtils::UpdateObjectFromFields($oObject, $aFields); 368 $oObject->DBUpdate(); 369 $oResult->AddObject(0, 'updated', $oObject, $aShowFields, $bExtendedOutput); 370 } 371 break; 372 373 case 'core/apply_stimulus': 374 RestUtils::InitTrackingComment($aParams); 375 $sClass = RestUtils::GetClass($aParams, 'class'); 376 $key = RestUtils::GetMandatoryParam($aParams, 'key'); 377 $aFields = RestUtils::GetMandatoryParam($aParams, 'fields'); 378 $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields'); 379 $bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+'); 380 $sStimulus = RestUtils::GetMandatoryParam($aParams, 'stimulus'); 381 382 // Note: the target class cannot be based on the result of FindObjectFromKey, because in case the user does not have read access, that function already fails with msg 'Nothing found' 383 $sTargetClass = RestUtils::GetObjectSetFromKey($sClass, $key)->GetFilter()->GetClass(); 384 if (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_MODIFY) != UR_ALLOWED_YES) 385 { 386 $oResult->code = RestResult::UNAUTHORIZED; 387 $oResult->message = "The current user does not have enough permissions for modifying data of class $sTargetClass"; 388 } 389 elseif (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_BULK_MODIFY) != UR_ALLOWED_YES) 390 { 391 $oResult->code = RestResult::UNAUTHORIZED; 392 $oResult->message = "The current user does not have enough permissions for massively modifying data of class $sTargetClass"; 393 } 394 else 395 { 396 $oObject = RestUtils::FindObjectFromKey($sClass, $key); 397 RestUtils::UpdateObjectFromFields($oObject, $aFields); 398 399 $aTransitions = $oObject->EnumTransitions(); 400 $aStimuli = MetaModel::EnumStimuli(get_class($oObject)); 401 if (!isset($aTransitions[$sStimulus])) 402 { 403 // Invalid stimulus 404 $oResult->code = RestResult::INTERNAL_ERROR; 405 $oResult->message = "Invalid stimulus: '$sStimulus' on the object ".$oObject->GetName()." in state '".$oObject->GetState()."'"; 406 } 407 else 408 { 409 $aTransition = $aTransitions[$sStimulus]; 410 $sTargetState = $aTransition['target_state']; 411 $aStates = MetaModel::EnumStates($sClass); 412 $aTargetStateDef = $aStates[$sTargetState]; 413 $aExpectedAttributes = $aTargetStateDef['attribute_list']; 414 415 $aMissingMandatory = array(); 416 foreach($aExpectedAttributes as $sAttCode => $iExpectCode) 417 { 418 if ( ($iExpectCode & OPT_ATT_MANDATORY) && ($oObject->Get($sAttCode) == '')) 419 { 420 $aMissingMandatory[] = $sAttCode; 421 } 422 } 423 if (count($aMissingMandatory) == 0) 424 { 425 // If all the mandatory fields are already present, just apply the transition silently... 426 if ($oObject->ApplyStimulus($sStimulus)) 427 { 428 $oObject->DBUpdate(); 429 $oResult->AddObject(0, 'updated', $oObject, $aShowFields, $bExtendedOutput); 430 } 431 } 432 else 433 { 434 // Missing mandatory attributes for the transition 435 $oResult->code = RestResult::INTERNAL_ERROR; 436 $oResult->message = 'Missing mandatory attribute(s) for applying the stimulus: '.implode(', ', $aMissingMandatory).'.'; 437 } 438 } 439 } 440 break; 441 442 case 'core/get': 443 $sClass = RestUtils::GetClass($aParams, 'class'); 444 $key = RestUtils::GetMandatoryParam($aParams, 'key'); 445 $aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields'); 446 $bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+'); 447 $iLimit = (int)RestUtils::GetOptionalParam($aParams, 'limit', 0); 448 $iPage = (int)RestUtils::GetOptionalParam($aParams, 'page', 1); 449 450 $oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key, $iLimit, self::getOffsetFromLimitAndPage($iLimit, $iPage)); 451 $sTargetClass = $oObjectSet->GetFilter()->GetClass(); 452 453 if (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ) != UR_ALLOWED_YES) 454 { 455 $oResult->code = RestResult::UNAUTHORIZED; 456 $oResult->message = "The current user does not have enough permissions for reading data of class $sTargetClass"; 457 } 458 elseif (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_BULK_READ) != UR_ALLOWED_YES) 459 { 460 $oResult->code = RestResult::UNAUTHORIZED; 461 $oResult->message = "The current user does not have enough permissions for exporting data of class $sTargetClass"; 462 } 463 elseif ($iPage < 1) 464 { 465 $oResult->code = RestResult::INVALID_PAGE; 466 $oResult->message = "The request page number is not valid. It must be an integer greater than 0"; 467 } 468 else 469 { 470 while ($oObject = $oObjectSet->Fetch()) 471 { 472 $oResult->AddObject(0, '', $oObject, $aShowFields, $bExtendedOutput); 473 } 474 $oResult->message = "Found: ".$oObjectSet->Count(); 475 } 476 break; 477 478 case 'core/delete': 479 $sClass = RestUtils::GetClass($aParams, 'class'); 480 $key = RestUtils::GetMandatoryParam($aParams, 'key'); 481 $bSimulate = RestUtils::GetOptionalParam($aParams, 'simulate', false); 482 483 $oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key); 484 $sTargetClass = $oObjectSet->GetFilter()->GetClass(); 485 486 if (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_DELETE) != UR_ALLOWED_YES) 487 { 488 $oResult->code = RestResult::UNAUTHORIZED; 489 $oResult->message = "The current user does not have enough permissions for deleting data of class $sTargetClass"; 490 } 491 elseif (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_DELETE) != UR_ALLOWED_YES) 492 { 493 $oResult->code = RestResult::UNAUTHORIZED; 494 $oResult->message = "The current user does not have enough permissions for massively deleting data of class $sTargetClass"; 495 } 496 else 497 { 498 $aObjects = $oObjectSet->ToArray(); 499 $this->DeleteObjects($oResult, $aObjects, $bSimulate); 500 } 501 break; 502 503 case 'core/get_related': 504 $oResult = new RestResultWithRelations(); 505 $sClass = RestUtils::GetClass($aParams, 'class'); 506 $key = RestUtils::GetMandatoryParam($aParams, 'key'); 507 $sRelation = RestUtils::GetMandatoryParam($aParams, 'relation'); 508 $iMaxRecursionDepth = RestUtils::GetOptionalParam($aParams, 'depth', 20 /* = MAX_RECURSION_DEPTH */); 509 $sDirection = RestUtils::GetOptionalParam($aParams, 'direction', null); 510 $bEnableRedundancy = RestUtils::GetOptionalParam($aParams, 'redundancy', false); 511 $bReverse = false; 512 513 if (is_null($sDirection) && ($sRelation == 'depends on')) 514 { 515 // Legacy behavior, consider "depends on" as a forward relation 516 $sRelation = 'impacts'; 517 $sDirection = 'up'; 518 $bReverse = true; // emulate the legacy behavior by returning the edges 519 } 520 else if(is_null($sDirection)) 521 { 522 $sDirection = 'down'; 523 } 524 525 $oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key); 526 if ($sDirection == 'down') 527 { 528 $oRelationGraph = $oObjectSet->GetRelatedObjectsDown($sRelation, $iMaxRecursionDepth, $bEnableRedundancy); 529 } 530 else if ($sDirection == 'up') 531 { 532 $oRelationGraph = $oObjectSet->GetRelatedObjectsUp($sRelation, $iMaxRecursionDepth, $bEnableRedundancy); 533 } 534 else 535 { 536 $oResult->code = RestResult::INTERNAL_ERROR; 537 $oResult->message = "Invalid value: '$sDirection' for the parameter 'direction'. Valid values are 'up' and 'down'"; 538 return $oResult; 539 540 } 541 542 if ($bEnableRedundancy) 543 { 544 // Remove the redundancy nodes from the output 545 $oIterator = new RelationTypeIterator($oRelationGraph, 'Node'); 546 foreach($oIterator as $oNode) 547 { 548 if ($oNode instanceof RelationRedundancyNode) 549 { 550 $oRelationGraph->FilterNode($oNode); 551 } 552 } 553 } 554 555 $aIndexByClass = array(); 556 $oIterator = new RelationTypeIterator($oRelationGraph); 557 foreach($oIterator as $oElement) 558 { 559 if ($oElement instanceof RelationObjectNode) 560 { 561 $oObject = $oElement->GetProperty('object'); 562 if ($oObject) 563 { 564 if ($bEnableRedundancy) 565 { 566 // Add only the "reached" objects 567 if ($oElement->GetProperty('is_reached')) 568 { 569 $aIndexByClass[get_class($oObject)][$oObject->GetKey()] = null; 570 $oResult->AddObject(0, '', $oObject); 571 } 572 } 573 else 574 { 575 $aIndexByClass[get_class($oObject)][$oObject->GetKey()] = null; 576 $oResult->AddObject(0, '', $oObject); 577 } 578 } 579 } 580 else if ($oElement instanceof RelationEdge) 581 { 582 $oSrcObj = $oElement->GetSourceNode()->GetProperty('object'); 583 $oDestObj = $oElement->GetSinkNode()->GetProperty('object'); 584 $sSrcKey = get_class($oSrcObj).'::'.$oSrcObj->GetKey(); 585 $sDestKey = get_class($oDestObj).'::'.$oDestObj->GetKey(); 586 if ($bEnableRedundancy) 587 { 588 // Add only the edges where both source and destination are "reached" 589 if ($oElement->GetSourceNode()->GetProperty('is_reached') && $oElement->GetSinkNode()->GetProperty('is_reached')) 590 { 591 if ($bReverse) 592 { 593 $oResult->AddRelation($sDestKey, $sSrcKey); 594 } 595 else 596 { 597 $oResult->AddRelation($sSrcKey, $sDestKey); 598 } 599 } 600 } 601 else 602 { 603 if ($bReverse) 604 { 605 $oResult->AddRelation($sDestKey, $sSrcKey); 606 } 607 else 608 { 609 $oResult->AddRelation($sSrcKey, $sDestKey); 610 } 611 } 612 } 613 } 614 615 if (count($aIndexByClass) > 0) 616 { 617 $aStats = array(); 618 $aUnauthorizedClasses = array(); 619 foreach ($aIndexByClass as $sClass => $aIds) 620 { 621 if (UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_READ) != UR_ALLOWED_YES) 622 { 623 $aUnauthorizedClasses[$sClass] = true; 624 } 625 $aStats[] = $sClass.'= '.count($aIds); 626 } 627 if (count($aUnauthorizedClasses) > 0) 628 { 629 $sClasses = implode(', ', array_keys($aUnauthorizedClasses)); 630 $oResult = new RestResult(); 631 $oResult->code = RestResult::UNAUTHORIZED; 632 $oResult->message = "The current user does not have enough permissions for exporting data of class(es): $sClasses"; 633 } 634 else 635 { 636 $oResult->message = "Scope: ".$oObjectSet->Count()."; Related objects: ".implode(', ', $aStats); 637 } 638 } 639 else 640 { 641 $oResult->message = "Nothing found"; 642 } 643 break; 644 645 case 'core/check_credentials': 646 $oResult = new RestResult(); 647 $sUser = RestUtils::GetMandatoryParam($aParams, 'user'); 648 $sPassword = RestUtils::GetMandatoryParam($aParams, 'password'); 649 650 if (UserRights::CheckCredentials($sUser, $sPassword) !== true) 651 { 652 $oResult->authorized = false; 653 } 654 else 655 { 656 $oResult->authorized = true; 657 } 658 break; 659 660 default: 661 // unknown operation: handled at a higher level 662 } 663 return $oResult; 664 } 665 666 /** 667 * Helper for object deletion 668 */ 669 public function DeleteObjects($oResult, $aObjects, $bSimulate) 670 { 671 $oDeletionPlan = new DeletionPlan(); 672 foreach($aObjects as $oObj) 673 { 674 if ($bSimulate) 675 { 676 $oObj->CheckToDelete($oDeletionPlan); 677 } 678 else 679 { 680 $oObj->DBDelete($oDeletionPlan); 681 } 682 } 683 684 foreach ($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes) 685 { 686 foreach ($aDeletes as $iId => $aData) 687 { 688 $oToDelete = $aData['to_delete']; 689 $bAutoDel = (($aData['mode'] == DEL_SILENT) || ($aData['mode'] == DEL_AUTO)); 690 if (array_key_exists('issue', $aData)) 691 { 692 if ($bAutoDel) 693 { 694 if (isset($aData['requested_explicitely'])) // i.e. in the initial list of objects to delete 695 { 696 $iCode = RestDelete::ISSUE; 697 $sPlanned = 'Cannot be deleted: '.$aData['issue']; 698 } 699 else 700 { 701 $iCode = RestDelete::AUTO_DELETE_ISSUE; 702 $sPlanned = 'Should be deleted automatically... but: '.$aData['issue']; 703 } 704 } 705 else 706 { 707 $iCode = RestDelete::REQUEST_EXPLICITELY; 708 $sPlanned = 'Must be deleted explicitely... but: '.$aData['issue']; 709 } 710 } 711 else 712 { 713 if ($bAutoDel) 714 { 715 if (isset($aData['requested_explicitely'])) 716 { 717 $iCode = RestDelete::OK; 718 $sPlanned = ''; 719 } 720 else 721 { 722 $iCode = RestDelete::AUTO_DELETE; 723 $sPlanned = 'Deleted automatically'; 724 } 725 } 726 else 727 { 728 $iCode = RestDelete::REQUEST_EXPLICITELY; 729 $sPlanned = 'Must be deleted explicitely'; 730 } 731 } 732 $oResult->AddObject($iCode, $sPlanned, $oToDelete); 733 } 734 } 735 foreach ($oDeletionPlan->ListUpdates() as $sRemoteClass => $aToUpdate) 736 { 737 foreach ($aToUpdate as $iId => $aData) 738 { 739 $oToUpdate = $aData['to_reset']; 740 if (array_key_exists('issue', $aData)) 741 { 742 $iCode = RestDelete::AUTO_UPDATE_ISSUE; 743 $sPlanned = 'Should be updated automatically... but: '.$aData['issue']; 744 } 745 else 746 { 747 $iCode = RestDelete::AUTO_UPDATE; 748 $sPlanned = 'Reset external keys: '.$aData['attributes_list']; 749 } 750 $oResult->AddObject($iCode, $sPlanned, $oToUpdate); 751 } 752 } 753 754 if ($oDeletionPlan->FoundStopper()) 755 { 756 if ($oDeletionPlan->FoundSecurityIssue()) 757 { 758 $iRes = RestResult::UNAUTHORIZED; 759 $sRes = 'Deletion not allowed on some objects'; 760 } 761 elseif ($oDeletionPlan->FoundManualOperation()) 762 { 763 $iRes = RestResult::UNSAFE; 764 $sRes = 'The deletion requires that other objects be deleted/updated, and those operations must be requested explicitely'; 765 } 766 else 767 { 768 $iRes = RestResult::INTERNAL_ERROR; 769 $sRes = 'Some issues have been encountered. See the list of planned changes for more information about the issue(s).'; 770 } 771 } 772 else 773 { 774 $iRes = RestResult::OK; 775 $sRes = 'Deleted: '.count($aObjects); 776 $iIndirect = $oDeletionPlan->GetTargetCount() - count($aObjects); 777 if ($iIndirect > 0) 778 { 779 $sRes .= ' plus (for DB integrity) '.$iIndirect; 780 } 781 } 782 $oResult->code = $iRes; 783 if ($bSimulate) 784 { 785 $oResult->message = 'SIMULATING: '.$sRes; 786 } 787 else 788 { 789 $oResult->message = $sRes; 790 } 791 } 792 793 /** 794 * @param int $iLimit 795 * @param int $iPage 796 * 797 * @return int Offset for a given page number 798 */ 799 protected static function getOffsetFromLimitAndPage($iLimit, $iPage) 800 { 801 return $iLimit * max(0, $iPage - 1); 802 } 803} 804