1<?php 2// Copyright (C) 2010-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 19require_once(APPROOT.'application/newsroomprovider.class.inc.php'); 20 21/** 22 * Management of application plugins 23 * 24 * Definition of interfaces that can be implemented to customize iTop. 25 * You may implement such interfaces in a module file (e.g. main.mymodule.php) 26 * 27 * @package Extensibility 28 * @copyright Copyright (C) 2010-2012 Combodo SARL 29 * @license http://opensource.org/licenses/AGPL-3.0 30 * @api 31 */ 32 33/** 34 * Implement this interface to change the behavior of the GUI for some objects. 35 * 36 * All methods are invoked by iTop for a given object. There are basically two usages: 37 * 38 * 1) To tweak the form of an object, you will have to implement a specific behavior within: 39 * 40 * * OnDisplayProperties (bEditMode = true) 41 * * OnFormSubmit 42 * * OnFormCancel 43 * 44 * 2) To tune the display of the object details, you can use: 45 * 46 * * OnDisplayProperties 47 * * OnDisplayRelations 48 * * GetIcon 49 * * GetHilightClass 50 * 51 * Please note that some of the APIs can be called several times for a single page displayed. 52 * Therefore it is not recommended to perform too many operations, such as querying the database. 53 * A recommended pattern is to cache data by the mean of static members. 54 * 55 * @package Extensibility 56 * @api 57 */ 58interface iApplicationUIExtension 59{ 60 /** 61 * Invoked when an object is being displayed (wiew or edit) 62 * 63 * The method is called right after the main tab has been displayed. 64 * You can add output to the page, either to change the display, or to add a form input 65 * 66 * Example: 67 * <code> 68 * if ($bEditMode) 69 * { 70 * $oPage->p('Age of the captain: <input type="text" name="captain_age"/>'); 71 * } 72 * else 73 * { 74 * $oPage->p('Age of the captain: '.$iCaptainAge); 75 * } 76 * </code> 77 * 78 * @param DBObject $oObject The object being displayed 79 * @param WebPage $oPage The output context 80 * @param boolean $bEditMode True if the edition form is being displayed 81 * @return void 82 */ 83 public function OnDisplayProperties($oObject, WebPage $oPage, $bEditMode = false); 84 85 /** 86 * Invoked when an object is being displayed (wiew or edit) 87 * 88 * The method is called rigth after all the tabs have been displayed 89 * 90 * @param DBObject $oObject The object being displayed 91 * @param WebPage $oPage The output context 92 * @param boolean $bEditMode True if the edition form is being displayed 93 * @return void 94 */ 95 public function OnDisplayRelations($oObject, WebPage $oPage, $bEditMode = false); 96 97 /** 98 * Invoked when the end-user clicks on Modify from the object edition form 99 * 100 * The method is called after the changes from the standard form have been 101 * taken into account, and before saving the changes into the database. 102 * 103 * @param DBObject $oObject The object being edited 104 * @param string $sFormPrefix Prefix given to the HTML form inputs 105 * @return void 106 */ 107 public function OnFormSubmit($oObject, $sFormPrefix = ''); 108 109 /** 110 * Invoked when the end-user clicks on Cancel from the object edition form 111 * 112 * Implement here any cleanup. This is necessary when you have injected some 113 * javascript into the edition form, and if that code requires to store temporary data 114 * (this is the case when a file must be uploaded). 115 * 116 * @param string $sTempId Unique temporary identifier made of session_id and transaction_id. It identifies the object in a unique way. 117 * @return void 118 */ 119 public function OnFormCancel($sTempId); 120 121 /** 122 * Not yet called by the framework! 123 * 124 * Sorry, the verb has been reserved. You must implement it, but it is not called as of now. 125 * 126 * @param DBObject $oObject The object being displayed 127 * 128 * @return string[] desc 129 */ 130 public function EnumUsedAttributes($oObject); // Not yet implemented 131 132 /** 133 * Not yet called by the framework! 134 * 135 * Sorry, the verb has been reserved. You must implement it, but it is not called as of now. 136 * 137 * @param DBObject $oObject The object being displayed 138 * @return string Path of the icon, relative to the modules directory. 139 */ 140 public function GetIcon($oObject); // Not yet implemented 141 142 /** 143 * Invoked when the object is displayed alone or within a list 144 * 145 * Returns a value influencing the appearance of the object depending on its 146 * state. 147 * 148 * Possible values are: 149 * 150 * * HILIGHT_CLASS_CRITICAL 151 * * HILIGHT_CLASS_WARNING 152 * * HILIGHT_CLASS_OK 153 * * HILIGHT_CLASS_NONE 154 * 155 * @param DBObject $oObject The object being displayed 156 * @return integer The value representing the mood of the object 157 */ 158 public function GetHilightClass($oObject); 159 160 /** 161 * Called when building the Actions menu for a single object or a list of objects 162 * 163 * Use this to add items to the Actions menu. You will have to specify a label and an URL. 164 * 165 * Example: 166 * <code> 167 * $oObject = $oSet->fetch(); 168 * if ($oObject instanceof Sheep) 169 * { 170 * return array('View in my app' => 'http://myserver/view_sheeps?id='.$oObject->Get('name')); 171 * } 172 * else 173 * { 174 * return array(); 175 * } 176 * </code> 177 * 178 * See also iPopupMenuExtension for greater flexibility 179 * 180 * @param DBObjectSet $oSet A set of persistent objects (DBObject) 181 * @return string[string] 182 */ 183 public function EnumAllowedActions(DBObjectSet $oSet); 184} 185 186/** 187 * Implement this interface to perform specific things when objects are manipulated 188 * 189 * Note that those methods will be called when objects are manipulated, either in a programmatic way 190 * or through the GUI. 191 * 192 * @package Extensibility 193 * @api 194 */ 195interface iApplicationObjectExtension 196{ 197 /** 198 * Invoked to determine wether an object has been modified in memory 199 * 200 * The GUI calls this verb to determine the message that will be displayed to the end-user. 201 * Anyhow, this API can be called in other contexts such as the CSV import tool. 202 * 203 * If the extension returns false, then the framework will perform the usual evaluation. 204 * Otherwise, the answer is definitively "yes, the object has changed". 205 * 206 * @param DBObject $oObject The target object 207 * @return boolean True if something has changed for the target object 208 */ 209 public function OnIsModified($oObject); 210 211 /** 212 * Invoked to determine wether an object can be written to the database 213 * 214 * The GUI calls this verb and reports any issue. 215 * Anyhow, this API can be called in other contexts such as the CSV import tool. 216 * 217 * @param DBObject $oObject The target object 218 * @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user. 219 */ 220 public function OnCheckToWrite($oObject); 221 222 /** 223 * Invoked to determine wether an object can be deleted from the database 224 * 225 * The GUI calls this verb and stops the deletion process if any issue is reported. 226 * 227 * Please not that it is not possible to cascade deletion by this mean: only stopper issues can be handled. 228 * 229 * @param DBObject $oObject The target object 230 * @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user. 231 */ 232 public function OnCheckToDelete($oObject); 233 234 /** 235 * Invoked when an object is updated into the database 236 * 237 * The method is called right <b>after</b> the object has been written to the database. 238 * 239 * @param DBObject $oObject The target object 240 * @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information once for all the changes made within the current page 241 * @return void 242 */ 243 public function OnDBUpdate($oObject, $oChange = null); 244 245 /** 246 * Invoked when an object is created into the database 247 * 248 * The method is called right <b>after</b> the object has been written to the database. 249 * 250 * @param DBObject $oObject The target object 251 * @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information once for all the changes made within the current page 252 * @return void 253 */ 254 public function OnDBInsert($oObject, $oChange = null); 255 256 /** 257 * Invoked when an object is deleted from the database 258 * 259 * The method is called right <b>before</b> the object will be deleted from the database. 260 * 261 * @param DBObject $oObject The target object 262 * @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information once for all the changes made within the current page 263 * @return void 264 */ 265 public function OnDBDelete($oObject, $oChange = null); 266} 267 268/** 269 * New extension to add menu items in the "popup" menus inside iTop. Provides a greater flexibility than 270 * iApplicationUIExtension::EnumAllowedActions. 271 * 272 * To add some menus into iTop, declare a class that implements this interface, it will be called automatically 273 * by the application, as long as the class definition is included somewhere in the code 274 * 275 * @package Extensibility 276 * @api 277 * @since 2.0 278 */ 279interface iPopupMenuExtension 280{ 281 /** 282 * Insert an item into the Actions menu of a list 283 * 284 * $param is a DBObjectSet containing the list of objects 285 */ 286 const MENU_OBJLIST_ACTIONS = 1; 287 /** 288 * Insert an item into the Toolkit menu of a list 289 * 290 * $param is a DBObjectSet containing the list of objects 291 */ 292 const MENU_OBJLIST_TOOLKIT = 2; 293 /** 294 * Insert an item into the Actions menu on an object details page 295 * 296 * $param is a DBObject instance: the object currently displayed 297 */ 298 const MENU_OBJDETAILS_ACTIONS = 3; 299 /** 300 * Insert an item into the Dashboard menu 301 * 302 * The dashboad menu is shown on the top right corner when a dashboard 303 * is being displayed. 304 * 305 * $param is a Dashboard instance: the dashboard currently displayed 306 */ 307 const MENU_DASHBOARD_ACTIONS = 4; 308 /** 309 * Insert an item into the User menu (upper right corner) 310 * 311 * $param is null 312 */ 313 const MENU_USER_ACTIONS = 5; 314 /** 315 * Insert an item into the Action menu on an object item in an objects list in the portal 316 * 317 * $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on the current line) 318 */ 319 const PORTAL_OBJLISTITEM_ACTIONS = 7; 320 /** 321 * Insert an item into the Action menu on an object details page in the portal 322 * 323 * $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object currently displayed) 324 */ 325 const PORTAL_OBJDETAILS_ACTIONS = 8; 326 327 /** 328 * Insert an item into the Actions menu of a list in the portal 329 * Note: This is not implemented yet ! 330 * 331 * $param is an array('portal_id' => $sPortalId, 'object_set' => $oSet) containing DBObjectSet containing the list of objects 332 * @todo 333 */ 334 const PORTAL_OBJLIST_ACTIONS = 6; 335 /** 336 * Insert an item into the user menu of the portal 337 * Note: This is not implemented yet ! 338 * 339 * $param is the portal id 340 * @todo 341 */ 342 const PORTAL_USER_ACTIONS = 9; 343 /** 344 * Insert an item into the navigation menu of the portal 345 * Note: This is not implemented yet ! 346 * 347 * $param is the portal id 348 * @todo 349 */ 350 const PORTAL_MENU_ACTIONS = 10; 351 352 /** 353 * Get the list of items to be added to a menu. 354 * 355 * This method is called by the framework for each menu. 356 * The items will be inserted in the menu in the order of the returned array. 357 * @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx 358 * @param mixed $param Depends on $iMenuId, see the constants defined above 359 * @return object[] An array of ApplicationPopupMenuItem or an empty array if no action is to be added to the menu 360 */ 361 public static function EnumItems($iMenuId, $param); 362} 363 364/** 365 * Base class for the various types of custom menus 366 * 367 * @package Extensibility 368 * @internal 369 * @since 2.0 370 */ 371abstract class ApplicationPopupMenuItem 372{ 373 /** @ignore */ 374 protected $sUID; 375 /** @ignore */ 376 protected $sLabel; 377 /** @ignore */ 378 protected $aCssClasses; 379 380 /** 381 * Constructor 382 * 383 * @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough 384 * @param string $sLabel The display label of the menu (must be localized) 385 * @param array $aCssClasses The CSS classes to add to the menu 386 */ 387 public function __construct($sUID, $sLabel) 388 { 389 $this->sUID = $sUID; 390 $this->sLabel = $sLabel; 391 $this->aCssClasses = array(); 392 } 393 394 /** 395 * Get the UID 396 * 397 * @return string The unique identifier 398 * @ignore 399 */ 400 public function GetUID() 401 { 402 return $this->sUID; 403 } 404 405 /** 406 * Get the label 407 * 408 * @return string The label 409 * @ignore 410 */ 411 public function GetLabel() 412 { 413 return $this->sLabel; 414 } 415 416 /** 417 * Get the CSS classes 418 * 419 * @return array 420 * @ignore 421 */ 422 public function GetCssClasses() 423 { 424 return $this->aCssClasses; 425 } 426 427 /** 428 * @param $aCssClasses 429 */ 430 public function SetCssClasses($aCssClasses) 431 { 432 $this->aCssClasses = $aCssClasses; 433 } 434 435 /** 436 * Adds a CSS class to the CSS classes that will be put on the menu item 437 * 438 * @param $sCssClass 439 */ 440 public function AddCssClass($sCssClass) 441 { 442 $this->aCssClasses[] = $sCssClass; 443 } 444 445 /** 446 * Returns the components to create a popup menu item in HTML 447 * 448 * @return array A hash array: array('label' => , 'url' => , 'target' => , 'onclick' => ) 449 * @ignore 450 */ 451 abstract public function GetMenuItem(); 452 453 /** @ignore */ 454 public function GetLinkedScripts() 455 { 456 return array(); 457 } 458} 459 460/** 461 * Class for adding an item into a popup menu that browses to the given URL 462 * 463 * @package Extensibility 464 * @api 465 * @since 2.0 466 */ 467class URLPopupMenuItem extends ApplicationPopupMenuItem 468{ 469 /** @ignore */ 470 protected $sURL; 471 /** @ignore */ 472 protected $sTarget; 473 474 /** 475 * Constructor 476 * 477 * @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough 478 * @param string $sLabel The display label of the menu (must be localized) 479 * @param string $sURL If the menu is an hyperlink, provide the absolute hyperlink here 480 * @param string $sTarget In case the menu is an hyperlink and a specific target is needed (_blank for example), pass it here 481 */ 482 public function __construct($sUID, $sLabel, $sURL, $sTarget = '_top') 483 { 484 parent::__construct($sUID, $sLabel); 485 $this->sURL = $sURL; 486 $this->sTarget = $sTarget; 487 } 488 489 /** @ignore */ 490 public function GetMenuItem() 491 { 492 return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget, 'css_classes' => $this->aCssClasses); 493 } 494} 495 496/** 497 * Class for adding an item into a popup menu that triggers some Javascript code 498 * 499 * @package Extensibility 500 * @api 501 * @since 2.0 502 */ 503class JSPopupMenuItem extends ApplicationPopupMenuItem 504{ 505 /** @ignore */ 506 protected $sJSCode; 507 /** @ignore */ 508 protected $aIncludeJSFiles; 509 510 /** 511 * Class for adding an item that triggers some Javascript code 512 * @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough 513 * @param string $sLabel The display label of the menu (must be localized) 514 * @param string $sJSCode In case the menu consists in executing some havascript code inside the page, pass it here. If supplied $sURL ans $sTarget will be ignored 515 * @param array $aIncludeJSFiles An array of file URLs to be included (once) to provide some JS libraries for the page. 516 */ 517 public function __construct($sUID, $sLabel, $sJSCode, $aIncludeJSFiles = array()) 518 { 519 parent::__construct($sUID, $sLabel); 520 $this->sJSCode = $sJSCode; 521 $this->aIncludeJSFiles = $aIncludeJSFiles; 522 } 523 524 /** @ignore */ 525 public function GetMenuItem() 526 { 527 // Note: the semicolumn is a must here! 528 return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#', 'css_classes' => $this->aCssClasses); 529 } 530 531 /** @ignore */ 532 public function GetLinkedScripts() 533 { 534 return $this->aIncludeJSFiles; 535 } 536} 537 538/** 539 * Class for adding a separator (horizontal line, not selectable) the output 540 * will automatically reduce several consecutive separators to just one 541 * 542 * @package Extensibility 543 * @api 544 * @since 2.0 545 */ 546class SeparatorPopupMenuItem extends ApplicationPopupMenuItem 547{ 548 static $idx = 0; 549 /** 550 * Constructor 551 */ 552 public function __construct() 553 { 554 parent::__construct('_separator_'.(self::$idx++), ''); 555 } 556 557 /** @ignore */ 558 public function GetMenuItem() 559 { 560 return array ('label' => '<hr class="menu-separator">', 'url' => '', 'css_classes' => $this->aCssClasses); 561 } 562} 563 564/** 565 * Class for adding an item as a button that browses to the given URL 566 * 567 * @package Extensibility 568 * @api 569 * @since 2.0 570 */ 571class URLButtonItem extends URLPopupMenuItem 572{ 573 574} 575 576/** 577 * Class for adding an item as a button that runs some JS code 578 * 579 * @package Extensibility 580 * @api 581 * @since 2.0 582 */ 583class JSButtonItem extends JSPopupMenuItem 584{ 585 586} 587 588/** 589 * Implement this interface to add content to any iTopWebPage 590 * 591 * There are 3 places where content can be added: 592 * 593 * * The north pane: (normaly empty/hidden) at the top of the page, spanning the whole 594 * width of the page 595 * * The south pane: (normaly empty/hidden) at the bottom of the page, spanning the whole 596 * width of the page 597 * * The admin banner (two tones gray background) at the left of the global search. 598 * Limited space, use it for short messages 599 * 600 * Each of the methods of this interface is supposed to return the HTML to be inserted at 601 * the specified place and can use the passed iTopWebPage object to add javascript or CSS definitions 602 * 603 * @package Extensibility 604 * @api 605 * @since 2.0 606 */ 607interface iPageUIExtension 608{ 609 /** 610 * Add content to the North pane 611 * @param iTopWebPage $oPage The page to insert stuff into. 612 * @return string The HTML content to add into the page 613 */ 614 public function GetNorthPaneHtml(iTopWebPage $oPage); 615 /** 616 * Add content to the South pane 617 * @param iTopWebPage $oPage The page to insert stuff into. 618 * @return string The HTML content to add into the page 619 */ 620 public function GetSouthPaneHtml(iTopWebPage $oPage); 621 /** 622 * Add content to the "admin banner" 623 * @param iTopWebPage $oPage The page to insert stuff into. 624 * @return string The HTML content to add into the page 625 */ 626 public function GetBannerHtml(iTopWebPage $oPage); 627} 628 629/** 630 * Implement this interface to add content to any enhanced portal page 631 * 632 * IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now! 633 * 634 * @package Extensibility 635 * @api 636 * @since 2.4 637 */ 638interface iPortalUIExtension 639{ 640 const ENUM_PORTAL_EXT_UI_BODY = 'Body'; 641 const ENUM_PORTAL_EXT_UI_NAVIGATION_MENU = 'NavigationMenu'; 642 const ENUM_PORTAL_EXT_UI_MAIN_CONTENT = 'MainContent'; 643 644 /** 645 * Returns an array of CSS file urls 646 * 647 * @param \Silex\Application $oApp 648 * @return array 649 */ 650 public function GetCSSFiles(\Silex\Application $oApp); 651 /** 652 * Returns inline (raw) CSS 653 * 654 * @param \Silex\Application $oApp 655 * @return string 656 */ 657 public function GetCSSInline(\Silex\Application $oApp); 658 /** 659 * Returns an array of JS file urls 660 * 661 * @param \Silex\Application $oApp 662 * @return array 663 */ 664 public function GetJSFiles(\Silex\Application $oApp); 665 /** 666 * Returns raw JS code 667 * 668 * @param \Silex\Application $oApp 669 * @return string 670 */ 671 public function GetJSInline(\Silex\Application $oApp); 672 /** 673 * Returns raw HTML code to put at the end of the <body> tag 674 * 675 * @param \Silex\Application $oApp 676 * @return string 677 */ 678 public function GetBodyHTML(\Silex\Application $oApp); 679 /** 680 * Returns raw HTML code to put at the end of the #main-wrapper element 681 * 682 * @param \Silex\Application $oApp 683 * @return string 684 */ 685 public function GetMainContentHTML(\Silex\Application $oApp); 686 /** 687 * Returns raw HTML code to put at the end of the #topbar and #sidebar elements 688 * 689 * @param \Silex\Application $oApp 690 * @return string 691 */ 692 public function GetNavigationMenuHTML(\Silex\Application $oApp); 693} 694 695/** 696 * IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now! 697 */ 698abstract class AbstractPortalUIExtension implements iPortalUIExtension 699{ 700 /** 701 * @inheritDoc 702 */ 703 public function GetCSSFiles(\Silex\Application $oApp) 704 { 705 return array(); 706 } 707 /** 708 * @inheritDoc 709 */ 710 public function GetCSSInline(\Silex\Application $oApp) 711 { 712 return null; 713 } 714 /** 715 * @inheritDoc 716 */ 717 public function GetJSFiles(\Silex\Application $oApp) 718 { 719 return array(); 720 } 721 /** 722 * @inheritDoc 723 */ 724 public function GetJSInline(\Silex\Application $oApp) 725 { 726 return null; 727 } 728 /** 729 * @inheritDoc 730 */ 731 public function GetBodyHTML(\Silex\Application $oApp) 732 { 733 return null; 734 } 735 /** 736 * @inheritDoc 737 */ 738 public function GetMainContentHTML(\Silex\Application $oApp) 739 { 740 return null; 741 } 742 /** 743 * @inheritDoc 744 */ 745 public function GetNavigationMenuHTML(\Silex\Application $oApp) 746 { 747 return null; 748 } 749} 750 751/** 752 * Implement this interface to add new operations to the REST/JSON web service 753 * 754 * @package Extensibility 755 * @api 756 * @since 2.0.1 757 */ 758interface iRestServiceProvider 759{ 760 /** 761 * Enumerate services delivered by this class 762 * @param string $sVersion The version (e.g. 1.0) supported by the services 763 * @return array An array of hash 'verb' => verb, 'description' => description 764 */ 765 public function ListOperations($sVersion); 766 767 /** 768 * Enumerate services delivered by this class 769 * 770 * @param string $sVersion The version (e.g. 1.0) supported by the services 771 * @param string $sVerb 772 * @param array $aParams 773 * 774 * @return RestResult The standardized result structure (at least a message) 775 */ 776 public function ExecOperation($sVersion, $sVerb, $aParams); 777} 778 779/** 780 * Minimal REST response structure. Derive this structure to add response data and error codes. 781 * 782 * @package Extensibility 783 * @api 784 * @since 2.0.1 785 */ 786class RestResult 787{ 788 /** 789 * Result: no issue has been encountered 790 */ 791 const OK = 0; 792 /** 793 * Result: missing/wrong credentials or the user does not have enough rights to perform the requested operation 794 */ 795 const UNAUTHORIZED = 1; 796 /** 797 * Result: the parameter 'version' is missing 798 */ 799 const MISSING_VERSION = 2; 800 /** 801 * Result: the parameter 'json_data' is missing 802 */ 803 const MISSING_JSON = 3; 804 /** 805 * Result: the input structure is not a valid JSON string 806 */ 807 const INVALID_JSON = 4; 808 /** 809 * Result: the parameter 'auth_user' is missing, authentication aborted 810 */ 811 const MISSING_AUTH_USER = 5; 812 /** 813 * Result: the parameter 'auth_pwd' is missing, authentication aborted 814 */ 815 const MISSING_AUTH_PWD = 6; 816 /** 817 * Result: no operation is available for the specified version 818 */ 819 const UNSUPPORTED_VERSION = 10; 820 /** 821 * Result: the requested operation is not valid for the specified version 822 */ 823 const UNKNOWN_OPERATION = 11; 824 /** 825 * Result: the requested operation cannot be performed because it can cause data (integrity) loss 826 */ 827 const UNSAFE = 12; 828 /** 829 * Result: the request page number is not valid. It must be an integer greater than 0 830 */ 831 const INVALID_PAGE = 13; 832 /** 833 * Result: the operation could not be performed, see the message for troubleshooting 834 */ 835 const INTERNAL_ERROR = 100; 836 837 /** 838 * Default constructor - ok! 839 * 840 * @param DBObject $oObject The object being reported 841 * @param string $sAttCode The attribute code (must be valid) 842 * @return string A scalar representation of the value 843 */ 844 public function __construct() 845 { 846 $this->code = RestResult::OK; 847 } 848 849 public $code; 850 public $message; 851} 852 853/** 854 * Helpers for implementing REST services 855 * 856 * @package Extensibility 857 * @api 858 */ 859class RestUtils 860{ 861 /** 862 * Registering tracking information. Any further object modification be associated with the given comment, when the modification gets recorded into the DB 863 * 864 * @param StdClass $oData Structured input data. Must contain 'comment'. 865 * @return void 866 * @throws Exception 867 * @api 868 */ 869 public static function InitTrackingComment($oData) 870 { 871 $sComment = self::GetMandatoryParam($oData, 'comment'); 872 CMDBObject::SetTrackInfo($sComment); 873 } 874 875 /** 876 * Read a mandatory parameter from from a Rest/Json structure. 877 * 878 * @param StdClass $oData Structured input data. Must contain the entry defined by sParamName. 879 * @param string $sParamName Name of the parameter to fetch from the input data 880 * 881 * @return mixed parameter value if present 882 * @throws Exception If the parameter is missing 883 * @api 884 */ 885 public static function GetMandatoryParam($oData, $sParamName) 886 { 887 if (isset($oData->$sParamName)) 888 { 889 return $oData->$sParamName; 890 } 891 else 892 { 893 throw new Exception("Missing parameter '$sParamName'"); 894 } 895 } 896 897 898 /** 899 * Read an optional parameter from from a Rest/Json structure. 900 * 901 * @param StdClass $oData Structured input data. 902 * @param string $sParamName Name of the parameter to fetch from the input data 903 * @param mixed $default Default value if the parameter is not found in the input data 904 * 905 * @return mixed 906 * @throws Exception 907 * @api 908 */ 909 public static function GetOptionalParam($oData, $sParamName, $default) 910 { 911 if (isset($oData->$sParamName)) 912 { 913 return $oData->$sParamName; 914 } 915 else 916 { 917 return $default; 918 } 919 } 920 921 922 /** 923 * Read a class from a Rest/Json structure. 924 * 925 * @param StdClass $oData Structured input data. Must contain the entry defined by sParamName. 926 * @param string $sParamName Name of the parameter to fetch from the input data 927 * 928 * @return string 929 * @throws Exception If the parameter is missing or the class is unknown 930 * @api 931 */ 932 public static function GetClass($oData, $sParamName) 933 { 934 $sClass = self::GetMandatoryParam($oData, $sParamName); 935 if (!MetaModel::IsValidClass($sClass)) 936 { 937 throw new Exception("$sParamName: '$sClass' is not a valid class'"); 938 } 939 return $sClass; 940 } 941 942 943 /** 944 * Read a list of attribute codes from a Rest/Json structure. 945 * 946 * @param string $sClass Name of the class 947 * @param StdClass $oData Structured input data. 948 * @param string $sParamName Name of the parameter to fetch from the input data 949 * 950 * @return array of class => list of attributes (see RestResultWithObjects::AddObject that uses it) 951 * @throws Exception 952 * @api 953 */ 954 public static function GetFieldList($sClass, $oData, $sParamName) 955 { 956 $sFields = self::GetOptionalParam($oData, $sParamName, '*'); 957 $aShowFields = array(); 958 if ($sFields == '*') 959 { 960 foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) 961 { 962 $aShowFields[$sClass][] = $sAttCode; 963 } 964 } 965 elseif ($sFields == '*+') 966 { 967 foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sRefClass) 968 { 969 foreach (MetaModel::ListAttributeDefs($sRefClass) as $sAttCode => $oAttDef) 970 { 971 $aShowFields[$sRefClass][] = $sAttCode; 972 } 973 } 974 } 975 else 976 { 977 foreach(explode(',', $sFields) as $sAttCode) 978 { 979 $sAttCode = trim($sAttCode); 980 if (($sAttCode != 'id') && (!MetaModel::IsValidAttCode($sClass, $sAttCode))) 981 { 982 throw new Exception("$sParamName: invalid attribute code '$sAttCode'"); 983 } 984 $aShowFields[$sClass][] = $sAttCode; 985 } 986 } 987 return $aShowFields; 988 } 989 990 /** 991 * Read and interpret object search criteria from a Rest/Json structure 992 * 993 * @param string $sClass Name of the class 994 * @param StdClass $oCriteria Hash of attribute code => value (can be a substructure or a scalar, depending on the nature of the attriute) 995 * @return object The object found 996 * @throws Exception If the input structure is not valid or it could not find exactly one object 997 */ 998 protected static function FindObjectFromCriteria($sClass, $oCriteria) 999 { 1000 $aCriteriaReport = array(); 1001 if (isset($oCriteria->finalclass)) 1002 { 1003 if (!MetaModel::IsValidClass($oCriteria->finalclass)) 1004 { 1005 throw new Exception("finalclass: Unknown class '".$oCriteria->finalclass."'"); 1006 } 1007 if (!MetaModel::IsParentClass($sClass, $oCriteria->finalclass)) 1008 { 1009 throw new Exception("finalclass: '".$oCriteria->finalclass."' is not a child class of '$sClass'"); 1010 } 1011 $sClass = $oCriteria->finalclass; 1012 } 1013 $oSearch = new DBObjectSearch($sClass); 1014 foreach ($oCriteria as $sAttCode => $value) 1015 { 1016 $realValue = static::MakeValue($sClass, $sAttCode, $value); 1017 $oSearch->AddCondition($sAttCode, $realValue, '='); 1018 if (is_object($value) || is_array($value)) 1019 { 1020 $value = json_encode($value); 1021 } 1022 $aCriteriaReport[] = "$sAttCode: $value ($realValue)"; 1023 } 1024 $oSet = new DBObjectSet($oSearch); 1025 $iCount = $oSet->Count(); 1026 if ($iCount == 0) 1027 { 1028 throw new Exception("No item found with criteria: ".implode(', ', $aCriteriaReport)); 1029 } 1030 elseif ($iCount > 1) 1031 { 1032 throw new Exception("Several items found ($iCount) with criteria: ".implode(', ', $aCriteriaReport)); 1033 } 1034 $res = $oSet->Fetch(); 1035 return $res; 1036 } 1037 1038 1039 /** 1040 * Find an object from a polymorph search specification (Rest/Json) 1041 * 1042 * @param string $sClass Name of the class 1043 * @param mixed $key Either search criteria (substructure), or an object or an OQL string. 1044 * @param bool $bAllowNullValue Allow the cases such as key = 0 or key = {null} and return null then 1045 * @return DBObject The object found 1046 * @throws Exception If the input structure is not valid or it could not find exactly one object 1047 * @api 1048 */ 1049 public static function FindObjectFromKey($sClass, $key, $bAllowNullValue = false) 1050 { 1051 if (is_object($key)) 1052 { 1053 $res = static::FindObjectFromCriteria($sClass, $key); 1054 } 1055 elseif (is_numeric($key)) 1056 { 1057 if ($bAllowNullValue && ($key == 0)) 1058 { 1059 $res = null; 1060 } 1061 else 1062 { 1063 $res = MetaModel::GetObject($sClass, $key, false); 1064 if (is_null($res)) 1065 { 1066 throw new Exception("Invalid object $sClass::$key"); 1067 } 1068 } 1069 } 1070 elseif (is_string($key)) 1071 { 1072 // OQL 1073 $oSearch = DBObjectSearch::FromOQL($key); 1074 $oSet = new DBObjectSet($oSearch); 1075 $iCount = $oSet->Count(); 1076 if ($iCount == 0) 1077 { 1078 throw new Exception("No item found for query: $key"); 1079 } 1080 elseif ($iCount > 1) 1081 { 1082 throw new Exception("Several items found ($iCount) for query: $key"); 1083 } 1084 $res = $oSet->Fetch(); 1085 } 1086 else 1087 { 1088 throw new Exception("Wrong format for key"); 1089 } 1090 return $res; 1091 } 1092 1093 /** 1094 * Search objects from a polymorph search specification (Rest/Json) 1095 * 1096 * @param string $sClass Name of the class 1097 * @param mixed $key Either search criteria (substructure), or an object or an OQL string. 1098 * @param int $iLimit The limit of results to return 1099 * @param int $iOffset The offset of results to return 1100 * 1101 * @return DBObjectSet The search result set 1102 * @throws Exception If the input structure is not valid 1103 */ 1104 public static function GetObjectSetFromKey($sClass, $key, $iLimit = 0, $iOffset = 0) 1105 { 1106 if (is_object($key)) 1107 { 1108 if (isset($key->finalclass)) 1109 { 1110 $sClass = $key->finalclass; 1111 if (!MetaModel::IsValidClass($sClass)) 1112 { 1113 throw new Exception("finalclass: Unknown class '$sClass'"); 1114 } 1115 } 1116 1117 $oSearch = new DBObjectSearch($sClass); 1118 foreach ($key as $sAttCode => $value) 1119 { 1120 $realValue = static::MakeValue($sClass, $sAttCode, $value); 1121 $oSearch->AddCondition($sAttCode, $realValue, '='); 1122 } 1123 } 1124 elseif (is_numeric($key)) 1125 { 1126 $oSearch = new DBObjectSearch($sClass); 1127 $oSearch->AddCondition('id', $key); 1128 } 1129 elseif (is_string($key)) 1130 { 1131 // OQL 1132 $oSearch = DBObjectSearch::FromOQL($key); 1133 } 1134 else 1135 { 1136 throw new Exception("Wrong format for key"); 1137 } 1138 $oObjectSet = new DBObjectSet($oSearch, array(), array(), null, $iLimit, $iOffset); 1139 return $oObjectSet; 1140 } 1141 1142 /** 1143 * Interpret the Rest/Json value and get a valid attribute value 1144 * 1145 * @param string $sClass Name of the class 1146 * @param string $sAttCode Attribute code 1147 * @param mixed $value Depending on the type of attribute (a scalar, or search criteria, or list of related objects...) 1148 * @return mixed The value that can be used with DBObject::Set() 1149 * @throws Exception If the specification of the value is not valid. 1150 * @api 1151 */ 1152 public static function MakeValue($sClass, $sAttCode, $value) 1153 { 1154 try 1155 { 1156 if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) 1157 { 1158 throw new Exception("Unknown attribute"); 1159 } 1160 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 1161 if ($oAttDef instanceof AttributeExternalKey) 1162 { 1163 $oExtKeyObject = static::FindObjectFromKey($oAttDef->GetTargetClass(), $value, true /* allow null */); 1164 $value = ($oExtKeyObject != null) ? $oExtKeyObject->GetKey() : 0; 1165 } 1166 elseif ($oAttDef instanceof AttributeLinkedSet) 1167 { 1168 if (!is_array($value)) 1169 { 1170 throw new Exception("A link set must be defined by an array of objects"); 1171 } 1172 $sLnkClass = $oAttDef->GetLinkedClass(); 1173 $aLinks = array(); 1174 foreach($value as $oValues) 1175 { 1176 $oLnk = static::MakeObjectFromFields($sLnkClass, $oValues); 1177 $aLinks[] = $oLnk; 1178 } 1179 $value = DBObjectSet::FromArray($sLnkClass, $aLinks); 1180 } 1181 elseif ($oAttDef instanceof AttributeTagSet) 1182 { 1183 if (!is_array($value)) 1184 { 1185 throw new Exception("A tag set must be defined by an array of tag codes"); 1186 } 1187 $value = $oAttDef->FromJSONToValue($value); 1188 } 1189 else 1190 { 1191 $value = $oAttDef->FromJSONToValue($value); 1192 } 1193 } 1194 catch (Exception $e) 1195 { 1196 throw new Exception("$sAttCode: ".$e->getMessage(), $e->getCode()); 1197 } 1198 return $value; 1199 } 1200 1201 /** 1202 * Interpret a Rest/Json structure that defines attribute values, and build an object 1203 * 1204 * @param string $sClass Name of the class 1205 * @param array $aFields A hash of attribute code => value specification. 1206 * @return DBObject The newly created object 1207 * @throws Exception If the specification of the values is not valid 1208 * @api 1209 */ 1210 public static function MakeObjectFromFields($sClass, $aFields) 1211 { 1212 $oObject = MetaModel::NewObject($sClass); 1213 foreach ($aFields as $sAttCode => $value) 1214 { 1215 $realValue = static::MakeValue($sClass, $sAttCode, $value); 1216 try 1217 { 1218 $oObject->Set($sAttCode, $realValue); 1219 } 1220 catch (Exception $e) 1221 { 1222 throw new Exception("$sAttCode: ".$e->getMessage(), $e->getCode()); 1223 } 1224 } 1225 return $oObject; 1226 } 1227 1228 /** 1229 * Interpret a Rest/Json structure that defines attribute values, and update the given object 1230 * 1231 * @param DBObject $oObject The object being modified 1232 * @param array $aFields A hash of attribute code => value specification. 1233 * @return DBObject The object modified 1234 * @throws Exception If the specification of the values is not valid 1235 * @api 1236 */ 1237 public static function UpdateObjectFromFields($oObject, $aFields) 1238 { 1239 $sClass = get_class($oObject); 1240 foreach ($aFields as $sAttCode => $value) 1241 { 1242 $realValue = static::MakeValue($sClass, $sAttCode, $value); 1243 try 1244 { 1245 $oObject->Set($sAttCode, $realValue); 1246 } 1247 catch (Exception $e) 1248 { 1249 throw new Exception("$sAttCode: ".$e->getMessage(), $e->getCode()); 1250 } 1251 } 1252 return $oObject; 1253 } 1254} 1255