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: &lt;input type="text" name="captain_age"/&gt;');
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