Browse Source

Trac #56 - Implemented and usable but still not a complete and dynamically generated WSDL file. Unit tests are in place (through client/server SOAP protocol, or directly onto the internal API)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@209 a333f486-631f-4898-b8df-5754b55c2be0
romainq 15 years ago
parent
commit
eabc55ab51

+ 26 - 4
core/test.class.inc.php

@@ -16,6 +16,7 @@ require_once('dbobjectset.class.php');
 
 
 require_once('userrights.class.inc.php');
 require_once('userrights.class.inc.php');
 
 
+require_once('../webservices/webservices.class.inc.php');
 
 
 
 
 // Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?)
 // Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?)
@@ -201,15 +202,16 @@ abstract class TestWebServices extends TestHandler
 {
 {
 	// simply overload DoExecute (temporary)
 	// simply overload DoExecute (temporary)
 
 
-	static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = '', $sOptionnalHeaders = null)
+	static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = 'admin', $sOptionnalHeaders = null)
 	{
 	{
 		$aDataAndAuth = $aData;
 		$aDataAndAuth = $aData;
 		$aDataAndAuth['operation'] = 'login';
 		$aDataAndAuth['operation'] = 'login';
 		$aDataAndAuth['auth_user'] = $sLogin;
 		$aDataAndAuth['auth_user'] = $sLogin;
 		$aDataAndAuth['auth_pwd'] = $sPassword;
 		$aDataAndAuth['auth_pwd'] = $sPassword;
-
-		$sHost = $GLOBALS['_SERVER']['HTTP_HOST'];
-		$sUrl = "http://$sHost/$sRelativeUrl";
+		$sHost = $_SERVER['HTTP_HOST'];
+		$sRawPath = $_SERVER['SCRIPT_NAME'];
+		$sPath = dirname($sRawPath);
+		$sUrl = "http://$sHost/$sPath/$sRelativeUrl";
 
 
 		return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders);
 		return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders);
 	}
 	}
@@ -249,6 +251,26 @@ abstract class TestWebServices extends TestHandler
 }
 }
 
 
 /**
 /**
+ * Test to execute a piece of code (checks if an error occurs)  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class TestSoapWebService extends TestHandler
+{
+	// simply overload DoExecute (temporary)
+
+	function __construct()
+	{
+		parent::__construct();
+	}
+}
+
+/**
  * Test to check that a function outputs some values depending on its input  
  * Test to check that a function outputs some values depending on its input  
  *
  *
  * @package     iTopORM
  * @package     iTopORM

+ 143 - 13
pages/testlist.inc.php

@@ -591,18 +591,8 @@ class TestMyBizModel extends TestBizModel
 // Test a complex biz model on the fly
 // Test a complex biz model on the fly
 ///////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////
 
 
-class TestQueriesOnFarm extends TestBizModel
+abstract class MyFarm extends TestBizModel
 {
 {
-	static public function GetName()
-	{
-		return 'Farm test';
-	}
-
-	static public function GetDescription()
-	{
-		return 'A series of tests on the farm business model (SQL generation)';
-	}
-	
 	static public function GetConfigFile() {return '../config-test-farm.php';}
 	static public function GetConfigFile() {return '../config-test-farm.php';}
 
 
 	protected function DoPrepare()
 	protected function DoPrepare()
@@ -673,6 +663,20 @@ class TestQueriesOnFarm extends TestBizModel
 		$iId = $oNew->DBInsertNoReload();
 		$iId = $oNew->DBInsertNoReload();
 		return $iId;
 		return $iId;
 	}
 	}
+}
+
+
+class TestQueriesOnFarm extends MyFarm
+{
+	static public function GetName()
+	{
+		return 'Farm test';
+	}
+
+	static public function GetDescription()
+	{
+		return 'A series of tests on the farm business model (SQL generation)';
+	}
 
 
 	protected function CheckQuery($sQuery, $bIsCorrectQuery)
 	protected function CheckQuery($sQuery, $bIsCorrectQuery)
 	{
 	{
@@ -941,6 +945,45 @@ class TestBulkChangeOnFarm extends TestBizModel
 
 
 
 
 ///////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////
+// Test data load
+///////////////////////////////////////////////////////////////////////////
+
+class TestFullTextSearchOnFarm extends MyFarm
+{
+	static public function GetName()
+	{
+		return 'Farm test - full text search';
+	}
+
+	static public function GetDescription()
+	{
+		return 'Focus on the full text search feature';
+	}
+	
+	protected function DoExecute()
+	{
+		echo "<h3>Create protagonists...</h3>";
+
+		$iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19');
+		$iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23');
+		$this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23');
+		$this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01');
+		$this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11');
+
+		$this->InsertBird('rooster', 'male', 12, 0, 0);
+		$this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35);
+
+		echo "<h3>Search...</h3>";
+		$oSearch = new DBObjectSearch('Mammal');
+		$oSearch->AddCondition_FullText('manof');
+		//$oResultSet = new DBObjectSet($oSearch);
+		$this->search_and_show_list($oSearch);
+		return true;
+	}
+}
+
+
+///////////////////////////////////////////////////////////////////////////
 // Benchmark queries
 // Benchmark queries
 ///////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////
 
 
@@ -1087,7 +1130,7 @@ class TestItopWebServices extends TestWebServices
 		$sCsvData = $aLoadSpec['csvdata'];
 		$sCsvData = $aLoadSpec['csvdata'];
 
 
 		$aPostData = array('class' => $sClass, 'csvdata' => $sCsvData);
 		$aPostData = array('class' => $sClass, 'csvdata' => $sCsvData);
-		$sRes = self::DoPostRequestAuth('webservices/import.php', $aPostData);
+		$sRes = self::DoPostRequestAuth('../webservices/import.php', $aPostData);
 
 
 		echo "<div><h3>$sTitle</h3><pre>$sCsvData</pre><div>$sRes</div></div>";
 		echo "<div><h3>$sTitle</h3><pre>$sCsvData</pre><div>$sRes</div></div>";
 	}
 	}
@@ -1110,7 +1153,7 @@ class TestItopWebServices extends TestWebServices
 			),
 			),
 			array(
 			array(
 				'class' => 'bizTeam',
 				'class' => 'bizTeam',
-				'csvdata' => "name;org_id;location_id\nSquadra Azzura;1;1"
+				'csvdata' => "name;org_id;location_name\nSquadra Azzura2;1;Paris"
 			),
 			),
 			array(
 			array(
 				'class' => 'bizWorkgroup',
 				'class' => 'bizWorkgroup',
@@ -1130,4 +1173,91 @@ class TestItopWebServices extends TestWebServices
 		return true;
 		return true;
 	}
 	}
 }
 }
+
+$aWebServices = array(
+	array(
+		'verb' => 'CreateIncidentTicket',
+		'args' => array(
+			'desc of ticket', /* sDescription */
+			'initial situation blah blah blah', /* sInitialSituation */
+			array('id' => 1), /* aCallerDesc */
+			array('id' => 2), /* aCustomerDesc */
+			array('id' => 1), /* aWorkgroupDesc */ 
+			array(
+				array(
+					'class' => 'logInfra',
+					'search' => array('id' => 108),
+					'link_values' => array('impactoche' => 'plus que critique'),
+				),
+				array(
+					'class' => 'bizDevice',
+					'search' => array('name' => 'Router03'),
+					'link_values' => array('impact' => 'ouais bof'),
+				),
+			), /* aImpact */
+			'low' /* sSeverity */
+		),
+	),
+);
+
+class TestSoap extends TestSoapWebService
+{
+	static public function GetName() {return 'Test SOAP';}
+	static public function GetDescription() {return 'Do basic stuff to test the SOAP capability';}
+
+	protected function DoExecute()
+	{
+		$this->m_SoapClient = new SoapClient(
+//			null,
+			"http://localhost:81/trunk/webservices/Itop.wsdl",
+			array(
+				//'location' => 'http://localhost:81/trunk/webservices/soapserver.php',
+				//'uri' => 'http://test-itop/',
+				'login' => 'admin',
+				'password' => 'admin',
+				// note: using the classmap functionality lead to APACHE fault on the server side
+				//'classmap' => array('stdClass' => 'ItopError'),
+				'trace' => 1,
+			)
+		);
+
+		global $aWebServices;
+		$aWebService = $aWebServices[0];
+
+//		$oRes = $this->m_SoapClient->CreateIncidentTicket();
+		$oRes = call_user_func_array(array($this->m_SoapClient, $aWebService['verb']), $aWebService['args']);
+
+		echo "<pre>\n";
+		print_r($oRes);
+		echo "</pre>\n";
+
+print "<pre>\n"; 
+print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
+print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
+print "</pre>"; 
+
+		return true;
+	}
+}
+
+class TestWebServicesDirect extends TestBizModel
+{
+	static public function GetName() {return 'Test web services locally';}
+	static public function GetDescription() {return 'Invoke the service directly (troubleshooting)';}
+
+	static public function GetConfigFile() {return '../config-itop.php';}
+
+	protected function DoExecute()
+	{
+		global $aWebServices;
+		$aWebService = $aWebServices[0];
+
+		$oWebServices = new WebServices();
+		$oRes = call_user_func_array(array($oWebServices, $aWebService['verb']), $aWebService['args']);
+		echo "<pre>\n";
+		print_r($oRes);
+		echo "</pre>\n";
+		return true;
+	}
+}
 ?>
 ?>

+ 4 - 0
webservices/itop.wsdl

@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- WSDL file generated by PHP WSDLCreator (http://www.protung.ro) -->
+<definitions name="WSDLItop" targetNamespace="urn:WSDLItop" xmlns:typens="urn:WSDLItop" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:typens0="http://localhost/php2swdl"><message name="CreateIncidentTicket"><part name="sDescription" type="xsd:anyType"></part><part name="sInitialSituation" type="xsd:anyType"></part><part name="aCallerDesc" type="xsd:anyType"></part><part name="aCustomerDesc" type="xsd:anyType"></part><part name="aWorkgroupDesc" type="xsd:anyType"></part><part name="aImpactedCIs" type="xsd:anyType"></part><part name="sSeverity" type="xsd:anyType"></part></message><message name="CreateIncidentTicketResponse"><part name="CreateIncidentTicketReturn" type="typens0:WebServiceResult"></part></message><portType name="WebServicesPortType"><operation name="CreateIncidentTicket"><documentation>Create an incident ticket from a monitoring system
+Some CIs might be specified (by their name/IP)</documentation><input message="typens:CreateIncidentTicket"></input><output message="typens:CreateIncidentTicketResponse"></output></operation></portType><binding name="WebServicesBinding" type="typens:WebServicesPortType"><soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"></soap:binding><operation name="CreateIncidentTicket"><soap:operation soapAction="urn:WebServicesAction"></soap:operation><input><soap:body namespace="urn:WSDLItop" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"></soap:body></input><output><soap:body namespace="urn:WSDLItop" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"></soap:body></output></operation></binding><service name="WSDLItopService"><port name="WebServicesPort" binding="typens:WebServicesBinding"><soap:address location="http://localhost:81/trunk/webservices/soapserver.php"></soap:address></port></service></definitions>

+ 56 - 0
webservices/soapserver.php

@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * SOAP-based web service 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+// Important note: if some required includes are missing, this might result
+// in the error "looks like we got no XML document"...
+
+require_once('../application/application.inc.php');
+require_once('../application/startup.inc.php');
+
+require('./webservices.class.inc.php');
+
+
+// pb ? - login_web_page::DoLogin(); // Check user rights and prompt if needed
+
+// Main program
+
+$oSoapServer = new SoapServer(
+	null,
+	//"http://localhost:81/trunk/webservices/Itop.wsdl", // to be a file generated dynamically with location = here
+	array(
+		'uri' => 'http://test-itop/',
+		// note: using the classmap and no WSDL spec causes a fault in APACHE (looks like an infinite loop)
+		//'classmap' => array('ItopErrorSOAP' => 'ItopError')
+	)
+);
+// $oSoapServer->setPersistence(SOAP_PERSISTENCE_SESSION);
+$oSoapServer->setClass('WebServices', null);
+
+if ($_SERVER["REQUEST_METHOD"] == "POST")
+{
+	$oSoapServer->handle();
+}
+else
+{
+	echo "This SOAP server can handle the following functions: ";
+	$aFunctions = $oSoapServer->getFunctions();
+	echo "<ul>\n";
+	foreach($aFunctions as $sFunc)
+	{
+		echo "<li>$sFunc</li>\n";
+	}
+	echo "</ul>\n";
+	echo "";
+}
+?>

+ 323 - 0
webservices/webservices.class.inc.php

@@ -0,0 +1,323 @@
+<?php
+
+/**
+ * Create Ticket web service
+ * Web Service API wrapper
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+class WebServiceResult
+{
+	
+	/**
+	 * Overall status
+	 *
+	 * @var m_bStatus
+	 */
+	public $m_bStatus;
+
+	/**
+	 * Error log
+	 *
+	 * @var m_aErrors
+	 */
+	public $m_aErrors;
+
+	/**
+	 * Warning log
+	 *
+	 * @var m_aWarnings
+	 */
+	public $m_aWarnings;
+
+	/**
+	 * Information log
+	 *
+	 * @var m_aInfos
+	 */
+	public $m_aInfos;
+
+	/**
+	 * Constructor
+	 *
+	 * @param status $bStatus
+	 */
+	public function __construct()
+	{
+		$this->m_bStatus = true;
+		$this->m_aErrors = array();
+		$this->m_aWarnings = array();
+		$this->m_aInfos = array();
+	}
+
+	/**
+	 * Did the current processing encounter a stopper issue ?
+	 *
+	 * @return bool
+	 */
+	public function IsOk()
+	{
+		return $this->m_bStatus;
+	}
+
+	/**
+	 * Log an error
+	 *
+	 * @param description $sDescription
+	 */
+	public function LogError($sDescription)
+	{
+		$this->m_aErrors[] = $sDescription;
+		$this->m_bStatus = false;
+	}
+
+	/**
+	 * Log a warning
+	 *
+	 * @param description $sDescription
+	 */
+	public function LogWarning($sDescription)
+	{
+		$this->m_aWarnings[] = $sDescription;
+	}
+
+	/**
+	 * Log an error or a warning
+	 *
+	 * @param string $sDescription
+	 * @param boolean $bIsStopper
+	 */
+	public function LogIssue($sDescription, $bIsStopper = true)
+	{
+		if ($bIsStopper) $this->LogError($sDescription);
+		else             $this->LogWarning($sDescription);
+	}
+
+	/**
+	 * Log operation details
+	 *
+	 * @param description $sDescription
+	 */
+	public function LogInfo($sDescription)
+	{
+		$this->m_aInfos[] = $sDescription;
+	}
+}
+
+class WebServices
+{
+	/**
+	 * Helper to set an external key
+	 *
+	 * @param string sAttCode
+	 * @param array aCallerDesc
+	 * @param DBObject oTargetObj
+	 * @param WebServiceResult oRes
+	 *
+	 */
+	protected function SetExternalKey($sAttCode, $aExtKeyDesc, &$oTargetObj, &$oRes)
+	{
+		$oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode);
+
+		$bIsMandatory = !$oExtKey->IsNullAllowed();
+		if (count($aExtKeyDesc) == 0)
+		{
+			$oRes->LogIssue("Ext key $sAttCode: no data was given to give a value to the key", $bIsMandatory);
+			return;
+		}
+
+		$sKeyClass = $oExtKey->GetTargetClass();
+		$oReconFilter = new CMDBSearchFilter($sKeyClass);
+		foreach ($aExtKeyDesc as $sForeignAttCode => $value)
+		{
+			if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode))
+			{
+				$sMsg = "Ext key $sAttCode: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass'";
+				$oRes->LogIssue($sMsg, $bIsMandatory);
+			}
+			// The foreign attribute is one of our reconciliation key
+			$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
+		}
+		$oExtObjects = new CMDBObjectSet($oReconFilter);
+		switch($oExtObjects->Count())
+		{
+		case 0:
+			$sMsg = "External key $sAttCode could not be found (searched: '".$oReconFilter->ToOQL()."')";
+			$oRes->LogIssue($sMsg, $bIsMandatory);
+			break;
+		case 1:
+			// Do change the external key attribute
+			$oForeignObj = $oExtObjects->Fetch();
+			$oTargetObj->Set($sAttCode, $oForeignObj->GetKey());
+
+			// Report it (no need to report if the object already had this value
+			if (array_key_exists($sAttCode, $oTargetObj->ListChanges()))
+			{
+				$oRes->LogInfo("$sAttCode has been set to ".$oForeignObj->GetKey());
+			}
+			break;
+		default:
+			$sMsg = "Found ".$oExtObjects->Count()." matches for external key $sAttCode (searched: '".$oReconFilter->ToOQL()."')";
+			$oRes->LogIssue($sMsg, $bIsMandatory);
+		}
+	}
+
+	/**
+	 * Helper to link objects
+	 *
+	 * @param string sLinkAttCode
+	 * @param string sLinkedClass
+	 * @param array $aLinkList
+	 * @param DBObject oTargetObj
+	 * @param WebServiceResult oRes
+	 *
+	 * @return array List of objects that could not be found
+	 */
+	protected function AddLinkedObjects($sLinkAttCode, $sLinkedClass, $aLinkList, &$oTargetObj, &$oRes)
+	{
+		$oLinkAtt = MetaModel::GetAttributeDef(get_class($oTargetObj), $sLinkAttCode);
+		$sLinkClass = $oLinkAtt->GetLinkedClass();
+		$sExtKeyToItem = $oLinkAtt->GetExtKeyToRemote();
+
+		$aItemsFound = array();
+		$aItemsNotFound = array();
+		foreach ($aLinkList as $aItemData)
+		{
+			$sTargetClass = $aItemData['class'];
+			if (!MetaModel::IsValidClass($sTargetClass))
+			{
+				$oRes->LogError("Invalid class $sTargetClass for impacted item");
+				continue; // skip
+			}
+			if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass))
+			{
+				$oRes->LogError("$sTargetClass is not a child class of $sLinkedClass");
+				continue; // skip
+			}
+			$oReconFilter = new CMDBSearchFilter($sTargetClass);
+			$aCIStringDesc = array();
+			foreach ($aItemData['search'] as $sAttCode => $value)
+			{
+				if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode))
+				{
+					$oRes->LogError("Invalid filter code $sAttCode for class $sTargetClass");
+					continue; // skip
+				}
+				$aCIStringDesc[] = "$sAttCode: $value";
+
+				// The attribute is one of our reconciliation key
+				$oReconFilter->AddCondition($sAttCode, $value, '=');
+			}
+			if (count($aCIStringDesc) == 1)
+			{
+				// take the last and unique value to describe the object
+				$sItemDesc = $value;
+			}
+			else
+			{
+				// describe the object by the given keys
+				$sItemDesc = $sTargetClass.'('.implode('/', $aCIStringDesc).')';
+			}
+
+			$oExtObjects = new CMDBObjectSet($oReconFilter);
+			switch($oExtObjects->Count())
+			{
+			case 0:
+				$oRes->LogWarning("Object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL()."')");
+				$aItemsNotFound[] = $sItemDesc;
+				break;
+			case 1:
+				$aItemsFound[] = array (
+					'object' => $oExtObjects->Fetch(),
+					'link_values' => @$aItemData['link_values'],
+					'desc' => $sItemDesc,
+				);
+				break;
+			default:
+				$oRes->LogWarning("Found ".$oExtObjects->Count()." matches for external key $sAttCode (searched: '".$oReconFilter->ToOQL()."')");
+				$aItemsNotFound[] = $sItemDesc;
+			}
+		}
+
+		if (count($aItemsFound) > 0)
+		{
+			$aLinks = array();
+			foreach($aItemsFound as $aItemData)
+			{
+				$oLink = MetaModel::NewObject($sLinkClass);
+				$oLink->Set($sExtKeyToItem, $aItemData['object']->GetKey());
+				foreach($aItemData['link_values'] as $sKey => $value)
+				{
+					if(!MetaModel::IsValidAttCode($sLinkClass, $sKey))
+					{
+						$oRes->LogWarning("Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'");
+					}
+					else
+					{
+						$oLink->Set($sKey, $value);
+					}
+				}
+				$aLinks[] = $oLink;
+			}
+			$oImpactedInfraSet = DBObjectSet::FromArray($sLinkClass, $aLinks);
+			$oTargetObj->Set($sLinkAttCode, $oImpactedInfraSet);
+		}
+
+		return $aItemsNotFound;
+	}
+
+	/**
+	 * Create an incident ticket from a monitoring system
+	 * Some CIs might be specified (by their name/IP)
+	 *	 
+	 * @param string sDecription
+	 * @param string sInitialSituation
+	 * @param array aCallerDesc
+	 * @param array aCustomerDesc
+	 * @param array aWorkgroupDesc
+	 * @param array aImpactedCIs
+	 * @param string sSeverity
+	 *
+	 * @return WebServiceResult
+	 */
+	function CreateIncidentTicket($sDescription, $sInitialSituation, $aCallerDesc, $aCustomerDesc, $aWorkgroupDesc, $aImpactedCIs, $sSeverity)
+	{
+		$oRes = new WebServiceResult();
+
+		new CMDBChange();
+		$oMyChange = MetaModel::NewObject("CMDBChange");
+		$oMyChange->Set("date", time());
+		$oMyChange->Set("userinfo", "Administrator");
+		$iChangeId = $oMyChange->DBInsertNoReload();
+
+		$oNewTicket = MetaModel::NewObject('bizIncidentTicket');
+		$oNewTicket->Set('title', $sDescription);
+		$oNewTicket->Set('initial_situation', $sInitialSituation);
+		$oNewTicket->Set('severity', $sSeverity);
+
+		$this->SetExternalKey('org_id', $aCustomerDesc, $oNewTicket, $oRes);
+		$this->SetExternalKey('caller_id', $aCallerDesc, $oNewTicket, $oRes);
+		$this->SetExternalKey('workgroup_id', $aWorkgroupDesc, $oNewTicket, $oRes);
+
+		$aDevicesNotFound = $this->AddLinkedObjects('impacted_infra_manual', 'logInfra', $aImpactedCIs, $oNewTicket, $oRes);
+		if (count($aDevicesNotFound) > 0)
+		{
+			$oTargetObj->Set('impact', implode(', ', $aDevicesNotFound));
+		}
+
+		if ($oRes->IsOk())
+		{
+			$iId = $oNewTicket->DBInsertTrackedNoReload($oMyChange);
+			$oRes->LogInfo("Created ticket #$iId");
+		}
+		return $oRes;
+	}
+}
+?>