Pārlūkot izejas kodu

Improved Web services: opened to services coming from an optional module

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@965 a333f486-631f-4898-b8df-5754b55c2be0
romainq 14 gadi atpakaļ
vecāks
revīzija
11e281c0d5

+ 20 - 0
core/config.class.inc.php

@@ -70,6 +70,7 @@ class Config
 
 	protected $m_aAppModules;
 	protected $m_aDataModels;
+	protected $m_aWebServiceCategories;
 	protected $m_aAddons;
 	protected $m_aDictionaries;
 
@@ -281,6 +282,9 @@ class Config
 			'core/trigger.class.inc.php',
 		);
 		$this->m_aDataModels = array();
+		$this->m_aWebServiceCategories = array(
+			'webservices/webservices.basic.php',
+		);
 		$this->m_aAddons = array(
 			// Default AddOn, always present can be moved to an official iTop Module later if needed
 			'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
@@ -405,6 +409,7 @@ class Config
 		}
 		$this->m_aAppModules = $MyModules['application'];
 		$this->m_aDataModels = $MyModules['business'];
+		$this->m_aWebServiceCategories = $MyModules['webservices'];
 		$this->m_aAddons = $MyModules['addons'];
 		$this->m_aDictionaries = $MyModules['dictionaries'];
 
@@ -489,6 +494,15 @@ class Config
 		$this->m_aDataModels = $aDataModels;
 	}
 
+	public function GetWebServiceCategories()
+	{
+		return $this->m_aWebServiceCategories;
+	}
+	public function SetWebServiceCategories($aWebServiceCategories)
+	{
+		$this->m_aWebServiceCategories = $aWebServiceCategories;
+	}
+
 	public function GetAddons()
 	{
 		return $this->m_aAddons;
@@ -856,6 +870,12 @@ class Config
 				fwrite($hFile, "\t\t'$sFile',\n");
 			}
 			fwrite($hFile, "\t),\n");
+			fwrite($hFile, "\t'webservices' => array (\n");
+			foreach($this->m_aWebServiceCategories as $sFile)
+			{
+				fwrite($hFile, "\t\t'$sFile',\n");
+			}
+			fwrite($hFile, "\t),\n");
 			fwrite($hFile, "\t'addons' => array (\n");
 			foreach($this->m_aAddons as $sKey => $sFile)
 			{

+ 24 - 0
core/dbobjectset.class.php

@@ -158,6 +158,30 @@ class DBObjectSet
 		return $aRet;
 	} 
 
+	public function ToArrayOfValues()
+	{
+		if (!$this->m_bLoaded) $this->Load();
+
+		$aRet = array();
+		foreach($this->m_aData as $iRow => $aObjects)
+		{
+			foreach($aObjects as $sClassAlias => $oObject)
+			{
+				$aRet[$iRow][$sClassAlias.'.'.'id'] = $oObject->GetKey(); 
+				$sClass = get_class($oObject);
+				foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
+				{
+					if ($oAttDef->IsScalar())
+					{
+						$sAttName = $sClassAlias.'.'.$sAttCode;
+						$aRet[$iRow][$sAttName] = $oObject->Get($sAttCode);
+					}
+				}
+			}
+		}
+		return $aRet;
+	} 
+
 	public function GetColumnAsArray($sAttCode, $bWithId = true)
 	{
 		$aRet = array();

+ 4 - 0
core/metamodel.class.php

@@ -3299,6 +3299,10 @@ abstract class MetaModel
 		{
 			self::Plugin($sConfigFile, 'business', $sToInclude);
 		}
+		foreach (self::$m_oConfig->GetWebServiceCategories() as $sModule => $sToInclude)
+		{
+			self::Plugin($sConfigFile, 'webservice', $sToInclude);
+		}
 		foreach (self::$m_oConfig->GetAddons() as $sModule => $sToInclude)
 		{
 			self::Plugin($sConfigFile, 'addons', $sToInclude);

+ 20 - 5
setup/index.php

@@ -668,23 +668,38 @@ function BuildConfig(SetupWebpage $oP, Config &$oConfig, $aParamValues)
 	$aAddOns = $oConfig->GetAddOns();
 	$aAppModules = $oConfig->GetAppModules();
 	$aDataModels = $oConfig->GetDataModels();
+	$aWebServiceCategories = $oConfig->GetWebServiceCategories();
 	$aDictionaries = $oConfig->GetDictionaries();
 	// Merge the values with the ones provided by the modules
 	// Make sure when don't load the same file twice...
 	foreach($aParamValues['module'] as $sModuleId)
 	{
 		$oP->log('Installed iTop module: '. $sModuleId);
-		$aDataModels = array_unique(array_merge($aDataModels, $aAvailableModules[$sModuleId]['datamodel']));
-		$aDictionaries = array_unique(array_merge($aDictionaries, $aAvailableModules[$sModuleId]['dictionary']));
-		foreach($aAvailableModules[$sModuleId]['settings'] as $sProperty => $value)
+		if (isset($aAvailableModules[$sModuleId]['datamodel']))
 		{
-			list($sName, $sVersion) = GetModuleName($sModuleId);
-			$oConfig->SetModuleSetting($sName, $sProperty, $value);
+			$aDataModels = array_unique(array_merge($aDataModels, $aAvailableModules[$sModuleId]['datamodel']));
+		}
+		if (isset($aAvailableModules[$sModuleId]['webservice']))
+		{
+			$aWebServiceCategories = array_unique(array_merge($aWebServiceCategories, $aAvailableModules[$sModuleId]['webservice']));
+		}
+		if (isset($aAvailableModules[$sModuleId]['dictionary']))
+		{
+			$aDictionaries = array_unique(array_merge($aDictionaries, $aAvailableModules[$sModuleId]['dictionary']));
+		}
+		if (isset($aAvailableModules[$sModuleId]['settings']))
+		{
+			foreach($aAvailableModules[$sModuleId]['settings'] as $sProperty => $value)
+			{
+				list($sName, $sVersion) = GetModuleName($sModuleId);
+				$oConfig->SetModuleSetting($sName, $sProperty, $value);
+			}
 		}
 	}
 	$oConfig->SetAddOns($aAddOns);
 	$oConfig->SetAppModules($aAppModules);
 	$oConfig->SetDataModels($aDataModels);
+	$oConfig->SetWebServiceCategories($aWebServiceCategories);
 	$oConfig->SetDictionaries($aDictionaries);
 }
 

+ 7 - 4
setup/setuppage.class.inc.php

@@ -259,7 +259,7 @@ table.formTable {
 	static $m_aModules = array();
 	
 	// All the entries below are list of file paths relative to the module directory
-	static $m_aFilesList = array('datamodel', 'dictionary', 'data.struct', 'data.sample');
+	static $m_aFilesList = array('datamodel', 'webservice', 'dictionary', 'data.struct', 'data.sample');
 
 	static $m_sModulePath = null;
 	public function SetModulePath($sModulePath)
@@ -281,11 +281,14 @@ table.formTable {
 
 		foreach(self::$m_aFilesList as $sAttribute)
 		{
-			// All the items below are list of files, that are relative to the current file
-			// being loaded, let's update their path to store path relative to the application directory
-			foreach(self::$m_aModules[$sId][$sAttribute] as $idx => $sRelativePath)
+			if (isset(self::$m_aModules[$sId][$sAttribute]))
 			{
+				// All the items below are list of files, that are relative to the current file
+				// being loaded, let's update their path to store path relative to the application directory
+				foreach(self::$m_aModules[$sId][$sAttribute] as $idx => $sRelativePath)
+				{
 				self::$m_aModules[$sId][$sAttribute][$idx] = self::$m_sModulePath.'/'.$sRelativePath;
+				}
 			}
 		}
 	}

+ 204 - 24
test/testlist.inc.php

@@ -1612,14 +1612,17 @@ class TestImportRESTMassive extends TestImportREST
 // Test SOAP services
 ///////////////////////////////////////////////////////////////////////////
 
-$aWebServices = array(
+$aCreateTicketSpecs = array(
 	array(
+		'service_category' => 'BasicServices',
 		'verb' => 'GetVersion',
-		'expected result' => WebServices::GetVersion(),
+//		'expected result' => '1.0.1',
+		'expected result' => '$ITOP_VERSION$ [dev]',
 		'explain result' => 'no comment!',
 		'args' => array(),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => true,
 		'explain result' => 'link attribute unknown + a CI not found',
@@ -1656,6 +1659,7 @@ $aWebServices = array(
 		),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => true,
 		'explain result' => 'caller not specified',
@@ -1682,6 +1686,7 @@ $aWebServices = array(
 		),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => false,
 		'explain result' => 'wrong class on CI to attach',
@@ -1708,6 +1713,7 @@ $aWebServices = array(
 		),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => false,
 		'explain result' => 'wrong search condition on CI to attach',
@@ -1734,6 +1740,7 @@ $aWebServices = array(
 		),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => true,
 		'explain result' => 'no CI to attach (empty array)',
@@ -1755,6 +1762,7 @@ $aWebServices = array(
 		),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => true,
 		'explain result' => 'no CI to attach (null)',
@@ -1775,6 +1783,7 @@ $aWebServices = array(
 		),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => true,
 		'explain result' => 'caller unknown',
@@ -1796,6 +1805,7 @@ $aWebServices = array(
 		),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => false,
 		'explain result' => 'wrong values for impact and urgency',
@@ -1817,6 +1827,7 @@ $aWebServices = array(
 		),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => false,
 		'explain result' => 'wrong password',
@@ -1838,6 +1849,7 @@ $aWebServices = array(
 		),
 	),
 	array(
+		'service_category' => '',
 		'verb' => 'CreateIncidentTicket',
 		'expected result' => false,
 		'explain result' => 'wrong login',
@@ -1861,41 +1873,145 @@ $aWebServices = array(
 );
 
 
-class TestSoap extends TestSoapWebService
+$aManageCloudUsersSpecs = array(
+	array(
+		'service_category' => '',
+		'verb' => 'SearchObjects',
+		'expected result' => false,
+		'explain result' => 'wrong OQL',
+		'args' => array(
+			'admin', /* sLogin */
+			'admin', /* sPassword */
+			'SELECT ThisClassDoesNotExist', /* sOQL */
+		),
+	),
+	array(
+		'service_category' => '',
+		'verb' => 'SearchObjects',
+		'expected result' => true,
+		'explain result' => 'ok',
+		'args' => array(
+			'admin', /* sLogin */
+			'admin', /* sPassword */
+			'SELECT Organization', /* sOQL */
+		),
+	),
+	array(
+		'service_category' => 'CloudUsersManagementService',
+		'verb' => 'CreateAccount',
+		'expected result' => true,
+		'explain result' => 'ok',
+		'args' => array(
+			'admin', /* sAdminLogin */
+			'admin', /* sAdminPassword */
+			'andros@combodo.com', /* sLogin */
+			'André', /* sFirstName */
+			'Dupont', /* sLastName */
+			1, /* iOrgId */
+			'FR FR', /* sLanguage */
+			array(
+				array(
+					new SOAPKeyValue('profile_id', '2'),
+					new SOAPKeyValue('reason', 'whynot'),
+				),
+				array(
+					new SOAPKeyValue('profile_id', '3'),
+					new SOAPKeyValue('reason', 'because'),
+				),
+			), /* aProfiles (array of key/value pairs) */
+			array(
+			), /* aAllowedOrgs (array of key/value pairs) */
+			'comment on the creation operation', /* sComment */
+		),
+	),
+	array(
+		'service_category' => 'CloudUsersManagementService',
+		'verb' => 'ModifyAccount',
+		'expected result' => true,
+		'explain result' => 'ok',
+		'args' => array(
+			'admin', /* sAdminLogin */
+			'admin', /* sAdminPassword */
+			'andros@combodo.com', /* sLogin */
+			'nono', /* sFirstName */
+			'robot', /* sLastName */
+			2, /* iOrgId */
+			'EN US', /* sLanguage */
+			array(
+				array(
+					new SOAPKeyValue('profile_id', '3'),
+					new SOAPKeyValue('reason', 'because'),
+				),
+			), /* aProfiles (array of key/value pairs) */
+			array(
+			), /* aAllowedOrgs (array of key/value pairs) */
+			'comment on the modify operation', /* sComment */
+		),
+	),
+	array(
+		'service_category' => 'CloudUsersManagementService',
+		'verb' => 'DeleteAccount',
+		'expected result' => true,
+		'explain result' => '',
+		'args' => array(
+			'admin', /* sAdminLogin */
+			'admin', /* sAdminPassword */
+			'andros@combodo.com', /* sLogin */
+			'comment on the deletion operation', /* sComment */
+		),
+	),
+	array(
+		'service_category' => 'CloudUsersManagementService',
+		'verb' => 'DeleteAccount',
+		'expected result' => false,
+		'explain result' => 'wrong login',
+		'args' => array(
+			'admin', /* sAdminLogin */
+			'admin', /* sAdminPassword */
+			'taratatata@sdf.com', /* sLogin */
+			'comment on the deletion operation', /* sComment */
+		),
+	),
+);
+
+abstract 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 $m_aTestSpecs;
+
 	protected function DoExecute()
 	{
 		echo "<p>Note: You may also want to try the sample SOAP client <a href=\"../webservices/itopsoap.examples.php\">itopsoap.examples.php</a></p>\n";
 
-		global $aSOAPMapping;
+		$aSOAPMapping = SOAPMapping::GetMapping();
 
 		// this file is generated dynamically with location = here
 		$sWsdlUri = 'http'.(isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!='off') ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php';
 
 		ini_set("soap.wsdl_cache_enabled","0");
-		$this->m_SoapClient = new SoapClient
-		(
-			$sWsdlUri,
-			array(
-				'classmap' => $aSOAPMapping,
-				'trace' => 1,
-			)
-		);
 
-		if (false)
-		{
-			self::DumpVariable($this->m_SoapClient->__getTypes());
-		} 
-
-		global $aWebServices;
-		foreach ($aWebServices as $iPos => $aWebService)
+		foreach ($this->m_aTestSpecs as $iPos => $aWebService)
 		{
 			echo "<h2>SOAP call #$iPos - {$aWebService['verb']}</h2>\n";
 			echo "<p>{$aWebService['explain result']}</p>\n";
 
+			$sWsdlUriForService = $sWsdlUri.'?service_category='.$aWebService['service_category'];
+			$this->m_SoapClient = new SoapClient
+			(
+				$sWsdlUriForService,
+				array(
+					'classmap' => $aSOAPMapping,
+					'trace' => 1,
+				)
+			);
+	
+			if (false)
+			{
+				self::DumpVariable($this->m_SoapClient->__getTypes());
+			} 
+
 			try
 			{
 				$oRes = call_user_func_array(array($this->m_SoapClient, $aWebService['verb']), $aWebService['args']);
@@ -1921,6 +2037,10 @@ class TestSoap extends TestSoapWebService
 			{
 				$res = $oRes->status;
 			}
+			elseif ($oRes instanceof SOAPSimpleResult)
+			{
+				$res = $oRes->status;
+			}
 			else
 			{
 				$res = $oRes;
@@ -1937,20 +2057,23 @@ class TestSoap extends TestSoapWebService
 	}
 }
 
-class TestWebServicesDirect extends TestBizModel
+abstract class TestSoapDirect 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 $m_aTestSpecs;
+
 	protected function DoExecute()
 	{
-		$oWebServices = new WebServices();
-
-		global $aWebServices;
-		foreach ($aWebServices as $iPos => $aWebService)
+		foreach ($this->m_aTestSpecs as $iPos => $aWebService)
 		{
+			$sServiceClass = $aWebService['service_category'];
+			if (empty($sServiceClass)) $sServiceClass = 'BasicServices';
+			$oWebServices = new $sServiceClass();
+
 			echo "<h2>SOAP call #$iPos - {$aWebService['verb']}</h2>\n";
 			echo "<p>{$aWebService['explain result']}</p>\n";
 			$oRes = call_user_func_array(array($oWebServices, $aWebService['verb']), $aWebService['args']);
@@ -1960,6 +2083,10 @@ class TestWebServicesDirect extends TestBizModel
 			{
 				$res = $oRes->status;
 			}
+			elseif ($oRes instanceof SOAPSimpleResult)
+			{
+				$res = $oRes->status;
+			}
 			else
 			{
 				$res = $oRes;
@@ -1977,6 +2104,59 @@ class TestWebServicesDirect extends TestBizModel
 	}
 }
 
+class TestSoap_Tickets extends TestSoap
+{
+	static public function GetName() {return 'Test SOAP - create ticket';}
+
+	protected function DoExecute()
+	{
+		global $aCreateTicketSpecs;
+		$this->m_aTestSpecs = $aCreateTicketSpecs;
+		return parent::DoExecute();
+	}
+}
+
+class TestSoapDirect_Tickets extends TestSoapDirect
+{
+	static public function GetName() {return 'Test SOAP without SOAP - create ticket';}
+
+	protected function DoExecute()
+	{
+		global $aCreateTicketSpecs;
+		$this->m_aTestSpecs = $aCreateTicketSpecs;
+		return parent::DoExecute();
+	}
+}
+
+
+class TestSoap_ManageCloudUsers extends TestSoap
+{
+	static public function GetName() {return 'Test SOAP - manage Cloud Users';}
+
+	protected function DoExecute()
+	{
+		global $aManageCloudUsersSpecs;
+		$this->m_aTestSpecs = $aManageCloudUsersSpecs;
+		return parent::DoExecute();
+	}
+}
+
+class TestSoapDirect_ManageCloudUsers extends TestSoapDirect
+{
+	static public function GetName() {return 'Test SOAP without SOAP - manage Cloud Users';}
+
+	protected function DoExecute()
+	{
+		global $aManageCloudUsersSpecs;
+		$this->m_aTestSpecs = $aManageCloudUsersSpecs;
+		return parent::DoExecute();
+	}
+}
+
+
+////////////////////// End of SOAP TESTS
+
+
 class TestTriggerAndEmail extends TestBizModel
 {
 	static public function GetName() {return 'Test trigger and email';}

+ 43 - 7
webservices/itop.wsdl.php

@@ -23,17 +23,53 @@
  * @license     http://www.opensource.org/licenses/gpl-3.0.html LGPL
  */
 
-// This is to make sure that the client will accept it....
-//
-header('Content-Type: application/xml; charset=UTF-8');
-//header('Content-Disposition: attachment; filename="itop.wsdl"');
-header('Content-Disposition: online; filename="itop.wsdl"');
+if (isset($_REQUEST['debug']))
+{
+	if ($_REQUEST['debug'] == 'text')
+	{
+		header('Content-Type: text/plain; charset=UTF-8');
+	}
+	else
+	{
+		header('Content-Type: application/xml; charset=UTF-8');
+	}
+}
+else
+{
+	// This is to make sure that the client will accept it....
+	//
+	header('Content-Type: application/xml; charset=UTF-8');
+	////header('Content-Disposition: attachment; filename="itop.wsdl"');
+	header('Content-Disposition: online; filename="itop.wsdl"');
+}
+
+require_once('../approot.inc.php');
+require_once(APPROOT.'webservices/webservices.class.inc.php');
+require_once(APPROOT.'core/config.class.inc.php');
 
-$sMyWsdl = './itop.wsdl.tpl';
+// Load the modules installed and enabled
+//
+$oConfig = new Config(APPROOT.'config-itop.php');
+$aFiles = $oConfig->GetWebServiceCategories();
+foreach ($aFiles as $sFile)
+{
+	require_once(APPROOT.$sFile);
+}
 
-$sRawFile = file_get_contents($sMyWsdl);
+if (isset($_REQUEST['service_category']) && (!empty($_REQUEST['service_category'])))
+{
+	$sRawFile = WebServicesBase::GetWSDLContents($_REQUEST['service_category']);
+}
+else
+{
+	$sRawFile = WebServicesBase::GetWSDLContents();
+}
 
 $sServerURI = 'http'.((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!='off')) ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/soapserver.php';
+if (isset($_REQUEST['service_category']) && (!empty($_REQUEST['service_category'])))
+{
+	$sServerURI .= "?service_category=".$_REQUEST['service_category'];
+}
 
 $sFinalFile = str_replace(
 	'___SOAP_SERVER_URI___',

+ 24 - 0
webservices/itop.wsdl.tpl

@@ -161,6 +161,14 @@
 	<message name="CreateIncidentTicketResponse">
 		<part name="CreateIncidentTicketReturn" type="typens:Result"/>
 	</message>
+	<message name="SearchObjects">
+		<part name="login" type="xsd:string"/>
+		<part name="password" type="xsd:string"/>
+		<part name="oql" type="xsd:string"/>
+	</message>
+	<message name="SearchObjectsResponse">
+		<part name="SearchObjectsReturn" type="typens:Result"/>
+	</message>
 	<portType name="WebServicePortType">
 		<operation name="GetVersion">
 			<wsdl:documentation>
@@ -177,6 +185,13 @@
 			<input message="typens:CreateIncidentTicket"/>
 			<output message="typens:CreateIncidentTicketResponse"/>
 		</operation>
+		<operation name="SearchObjects">
+			<wsdl:documentation>
+				Create a ticket, return information about reconciliation on external keys and the created ticket
+			</wsdl:documentation> -->
+			<input message="typens:SearchObjects"/>
+			<output message="typens:SearchObjectsResponse"/>
+		</operation>
 	</portType>
 	<binding name="WebServiceBinding" type="typens:WebServicePortType">
 		<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
@@ -198,6 +213,15 @@
 				<soap:body namespace="urn:ITop" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
 			</output>
 		</operation>
+		<operation name="SearchObjects">
+			<soap:operation soapAction="urn:WebServiceAction"/>
+			<input>
+				<soap:body namespace="urn:ITop" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
+			</input>
+			<output>
+				<soap:body namespace="urn:ITop" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
+			</output>
+		</operation>
 	</binding>
 	<service name="ITopService">
 		<wsdl:documentation>

+ 51 - 0
webservices/itopsoap.examples.php

@@ -28,6 +28,9 @@ require_once('itopsoaptypes.class.inc.php');
 
 $sItopRoot = 'http'.((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!='off')) ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/..';
 $sWsdlUri = $sItopRoot.'/webservices/itop.wsdl.php';
+//$sWsdlUri .= '?service_category=';
+
+$aSOAPMapping = SOAPMapping::GetMapping();
 
 ini_set("soap.wsdl_cache_enabled","0");
 $oSoapClient = new SoapClient(
@@ -81,6 +84,54 @@ try
 	print_r($oRes);
 	echo "</pre>\n";
 	echo "</p>\n";
+
+	$oRes = $oSoapClient->SearchObjects
+	(
+		'admin', /* login */
+		'admin', /* password */
+		'SELECT URP_Profiles' /* oql */
+	);
+
+	echo "<p>SearchObjects() returned:\n";
+	if ($oRes->status)
+	{
+		$aResults = $oRes->result;
+
+		echo "<table>\n";
+
+		// Header made after the first line
+		echo "<tr>\n";
+		foreach ($aResults[0]->values as $aKeyValuePair)
+		{
+			echo "   <th>".$aKeyValuePair->key."</th>\n";
+		}
+		echo "</tr>\n";
+
+		foreach ($aResults as $iRow => $aData)
+		{
+			echo "<tr>\n";
+			foreach ($aData->values as $aKeyValuePair)
+			{
+				echo "   <td>".$aKeyValuePair->value."</td>\n";
+			}
+			echo "</tr>\n";
+		}
+		echo "</table>\n";
+	}
+	else
+	{
+		$aErrors = array();
+		foreach ($oRes->errors->messages as $oMessage)
+		{
+			$aErrors[] = $oMessage->text;
+		}
+		$sErrorMsg = implode(', ', $aErrors);
+		echo "<p>SearchObjects() failed with message: $sErrorMsg</p>\n";
+		//echo "<pre>\n";
+		//print_r($oRes);
+		//echo "</pre>\n";
+	}
+	echo "</p>\n";
 }
 catch(SoapFault $e)
 {

+ 34 - 14
webservices/itopsoaptypes.class.inc.php

@@ -107,7 +107,7 @@ class SOAPResultLog
 }
 
 
-class SOAPResultData
+class SOAPKeyValue
 {
 	public $key; // string
 	public $value; // string
@@ -119,11 +119,10 @@ class SOAPResultData
 	}
 }
 
-
 class SOAPResultMessage
 {
 	public $label; // string
-	public $values; // array of SOAPResultData
+	public $values; // array of SOAPKeyValue
 
 	public function __construct($sLabel, $aValues)
 	{
@@ -151,17 +150,38 @@ class SOAPResult
 	}
 }
 
-$aSOAPMapping = array(
-	'SearchCondition' => 'SOAPSearchCondition',
-	'ExternalKeySearch' => 'SOAPExternalKeySearch',
-	'AttributeValue' => 'SOAPAttributeValue',
-	'LinkCreationSpec' => 'SOAPLinkCreationSpec',
-	'LogMessage' => 'SOAPLogMessage',
-	'ResultLog' => 'SOAPResultLog',
-	'ResultData' => 'SOAPResultData',
-	'ResultMessage' => 'SOAPResultMessage',
-	'Result' => 'SOAPResult',
-);
+class SOAPSimpleResult
+{
+	public $status; // boolean
+	public $message; // string
 
+	public function __construct($bStatus, $sMessage)
+	{
+		$this->status = $bStatus;
+		$this->message = $sMessage;
+	}
+}
+
+
+class SOAPMapping
+{
+	static function GetMapping()
+	{
+		$aSOAPMapping = array(
+			'SearchCondition' => 'SOAPSearchCondition',
+			'ExternalKeySearch' => 'SOAPExternalKeySearch',
+			'AttributeValue' => 'SOAPAttributeValue',
+			'LinkCreationSpec' => 'SOAPLinkCreationSpec',
+			'KeyValue' => 'SOAPKeyValue',
+			'LogMessage' => 'SOAPLogMessage',
+			'ResultLog' => 'SOAPResultLog',
+			'ResultData' => 'SOAPKeyValue',
+			'ResultMessage' => 'SOAPResultMessage',
+			'Result' => 'SOAPResult',
+			'SimpleResult' => 'SOAPSimpleResult',
+		);
+		return $aSOAPMapping;
+	}
+}
 
 ?>

+ 43 - 3
webservices/soapserver.php

@@ -30,14 +30,17 @@ require_once('../approot.inc.php');
 require_once(APPROOT.'/application/application.inc.php');
 require_once(APPROOT.'/application/startup.inc.php');
 
-require('./webservices.class.inc.php');
-
 // this file is generated dynamically with location = here
 $sWsdlUri = 'http'.((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!='off')) ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php';
+if (isset($_REQUEST['service_category']) && (!empty($_REQUEST['service_category'])))
+{
+	$sWsdlUri .= "?service_category=".$_REQUEST['service_category'];
+}
 
 
 ini_set("soap.wsdl_cache_enabled","0");
 
+$aSOAPMapping = SOAPMapping::GetMapping();
 $oSoapServer = new SoapServer
 (
 	$sWsdlUri,
@@ -46,7 +49,28 @@ $oSoapServer = new SoapServer
 	)
 );
 // $oSoapServer->setPersistence(SOAP_PERSISTENCE_SESSION);
-$oSoapServer->setClass('WebServices', null);
+if (isset($_REQUEST['service_category']) && (!empty($_REQUEST['service_category'])))
+{
+	$sServiceClass = $_REQUEST['service_category'];
+	if (!class_exists($sServiceClass))
+	{
+		// not a valid class name (not a PHP class at all)
+		throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not a PHP class");
+	}
+	elseif (!is_subclass_of($sServiceClass, 'WebServicesBase'))
+	{
+		// not a valid class name (not deriving from WebServicesBase)
+		throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not derived from WebServicesBase");
+	}
+	else
+	{
+		$oSoapServer->setClass($sServiceClass, null);
+	}
+}
+else
+{
+	$oSoapServer->setClass('BasicServices', null);
+}
 
 if ($_SERVER["REQUEST_METHOD"] == "POST")
 {
@@ -59,9 +83,25 @@ else
 	echo "<ul>\n";
 	foreach($aFunctions as $sFunc)
 	{
+		if ($sFunc == 'GetWSDLContents') continue;
+
 		echo "<li>$sFunc</li>\n";
 	}
 	echo "</ul>\n";
 	echo "<p>Here the <a href=\"$sWsdlUri\">WSDL file</a><p>";
+
+	echo "You may also want to try the following service categories: ";
+	echo "<ul>\n";
+	foreach(get_declared_classes() as $sPHPClass)
+	{
+		if (is_subclass_of($sPHPClass, 'WebServicesBase'))
+		{
+			$sServiceCategory = $sPHPClass;
+			$sSoapServerUri = 'http'.((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!='off')) ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/soapserver.php';
+			$sSoapServerUri .= "?service_category=$sServiceCategory";
+			echo "<li><a href=\"$sSoapServerUri\">$sServiceCategory</a></li>\n";
+		}
+	}
+	echo "</ul>\n";
 }
 ?>

+ 215 - 0
webservices/webservices.basic.php

@@ -0,0 +1,215 @@
+<?php
+// Copyright (C) 2010 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+/**
+ * Implementation of iTop SOAP services
+ *
+ * @author      Erwan Taloc <erwan.taloc@combodo.com>
+ * @author      Romain Quetiez <romain.quetiez@combodo.com>
+ * @author      Denis Flaven <denis.flaven@combodo.com>
+ * @license     http://www.opensource.org/licenses/gpl-3.0.html LGPL
+ */
+
+require_once(APPROOT.'/webservices/webservices.class.inc.php');
+
+
+class BasicServices extends WebServicesBase
+{
+	static protected function GetWSDLFilePath()
+	{
+		return APPROOT.'/webservices/itop.wsdl.tpl';
+	}
+
+	/**
+	 * Get the server version (TODO: get it dynamically, where ?)
+	 *	 
+	 * @return WebServiceResult
+	 */
+	static public function GetVersion()
+	{
+		if (ITOP_REVISION == '$WCREV$')
+		{
+			$sVersionString = ITOP_VERSION.' [dev]';
+		}
+		else
+		{
+			// This is a build made from SVN, let display the full information
+			$sVersionString = ITOP_VERSION."-".ITOP_REVISION." ".ITOP_BUILD_DATE;
+		}
+
+		return $sVersionString;
+	}
+
+	public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency)
+	{
+		if (!UserRights::CheckCredentials($sLogin, $sPassword))
+		{
+			$oRes = new WebServiceResultFailedLogin($sLogin);
+			$this->LogUsage(__FUNCTION__, $oRes);
+
+			return $oRes->ToSoapStructure();
+		}
+		UserRights::Login($sLogin);
+
+		$aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc);
+		$aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc);
+		$aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc);
+		$aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc);
+		$aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc);
+
+		$aImpactedCIs = array();
+		if (is_null($aSOAPImpactedCIs)) $aSOAPImpactedCIs = array();
+		foreach($aSOAPImpactedCIs as $oImpactedCIs)
+		{
+			$aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs);
+		}
+
+		$oRes = $this->_CreateIncidentTicket
+		(
+			$sTitle,
+			$sDescription,
+			$aCallerDesc,
+			$aCustomerDesc,
+			$aServiceDesc,
+			$aServiceSubcategoryDesc,
+			$sProduct,
+			$aWorkgroupDesc,
+			$aImpactedCIs,
+			$sImpact,
+			$sUrgency
+		);
+		return $oRes->ToSoapStructure();
+	}
+
+	/**
+	 * Create an incident ticket from a monitoring system
+	 * Some CIs might be specified (by their name/IP)
+	 *	 
+	 * @param string sTitle
+	 * @param string sDescription
+	 * @param array aCallerDesc
+	 * @param array aCustomerDesc
+	 * @param array aServiceDesc
+	 * @param array aServiceSubcategoryDesc
+	 * @param string sProduct
+	 * @param array aWorkgroupDesc
+	 * @param array aImpactedCIs
+	 * @param string sImpact
+	 * @param string sUrgency
+	 *
+	 * @return WebServiceResult
+	 */
+	protected function _CreateIncidentTicket($sTitle, $sDescription, $aCallerDesc, $aCustomerDesc, $aServiceDesc, $aServiceSubcategoryDesc, $sProduct, $aWorkgroupDesc, $aImpactedCIs, $sImpact, $sUrgency)
+	{
+
+		$oRes = new WebServiceResult();
+
+		try
+		{
+			$oMyChange = MetaModel::NewObject("CMDBChange");
+			$oMyChange->Set("date", time());
+			$oMyChange->Set("userinfo", "Administrator");
+			$iChangeId = $oMyChange->DBInsertNoReload();
+	
+			$oNewTicket = MetaModel::NewObject('Incident');
+			$this->MyObjectSetScalar('title', 'title', $sTitle, $oNewTicket, $oRes);
+			$this->MyObjectSetScalar('description', 'description', $sDescription, $oNewTicket, $oRes);
+
+			$this->MyObjectSetExternalKey('org_id', 'customer', $aCustomerDesc, $oNewTicket, $oRes);
+			$this->MyObjectSetExternalKey('caller_id', 'caller', $aCallerDesc, $oNewTicket, $oRes);
+	
+			$this->MyObjectSetExternalKey('service_id', 'service', $aServiceDesc, $oNewTicket, $oRes);
+			$this->MyObjectSetExternalKey('servicesubcategory_id', 'servicesubcategory', $aServiceSubcategoryDesc, $oNewTicket, $oRes);
+			$this->MyObjectSetScalar('product', 'product', $sProduct, $oNewTicket, $oRes);
+
+			$this->MyObjectSetExternalKey('workgroup_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes);
+
+
+			$aDevicesNotFound = $this->AddLinkedObjects('ci_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes);
+			if (count($aDevicesNotFound) > 0)
+			{
+				$this->MyObjectSetScalar('description', 'n/a', $sDescription.' - Related CIs: '.implode(', ', $aDevicesNotFound), $oNewTicket, $oRes);
+			}
+			else
+			{
+				$this->MyObjectSetScalar('description', 'n/a', $sDescription, $oNewTicket, $oRes);
+			}
+
+			$this->MyObjectSetScalar('impact', 'impact', $sImpact, $oNewTicket, $oRes);
+			$this->MyObjectSetScalar('urgency', 'urgency', $sUrgency, $oNewTicket, $oRes);
+
+			$this->MyObjectInsert($oNewTicket, 'created', $oMyChange, $oRes);
+		}
+		catch (CoreException $e)
+		{
+			$oRes->LogError($e->getMessage());
+		}
+		catch (Exception $e)
+		{
+			$oRes->LogError($e->getMessage());
+		}
+
+		$this->LogUsage(__FUNCTION__, $oRes);
+		return $oRes;
+	}
+
+	/**
+	 * Given an OQL, returns a set of objects (several objects could be on the same row)
+	 *	 
+	 * @param string sOQL
+	 */	 
+	public function SearchObjects($sLogin, $sPassword, $sOQL)
+	{
+		if (!UserRights::CheckCredentials($sLogin, $sPassword))
+		{
+			$oRes = new WebServiceResultFailedLogin($sLogin);
+			$this->LogUsage(__FUNCTION__, $oRes);
+
+			return $oRes->ToSoapStructure();
+		}
+		UserRights::Login($sLogin);
+
+		$oRes = $this->_SearchObjects($sOQL);
+		return $oRes->ToSoapStructure();
+	}
+
+	protected function _SearchObjects($sOQL)
+	{
+		$oRes = new WebServiceResult();
+		try
+		{
+			$oSearch = DBObjectSearch::FromOQL($sOQL);
+			$oSet = new DBObjectSet($oSearch);
+			$aData = $oSet->ToArrayOfValues();
+			foreach($aData as $iRow => $aRow)
+			{
+				$oRes->AddResultRow("row_$iRow", $aRow);
+			}
+		}
+		catch (CoreException $e)
+		{
+			$oRes->LogError($e->getMessage());
+		}
+		catch (Exception $e)
+		{
+			$oRes->LogError($e->getMessage());
+		}
+
+		$this->LogUsage(__FUNCTION__, $oRes);
+		return $oRes;
+	}
+}
+?>

+ 32 - 128
webservices/webservices.class.inc.php

@@ -84,7 +84,7 @@ class WebServiceResult
 			$aValues = array();
 			foreach($aData as $sKey => $value)
 			{
-				$aValues[] = new SoapResultData($sKey, $value);
+				$aValues[] = new SOAPKeyValue($sKey, $value);
 			}
 			$aResults[] = new SoapResultMessage($sLabel, $aValues);
 		}
@@ -141,6 +141,17 @@ class WebServiceResult
 	}
 
 	/**
+	 * Add result details - a table row
+	 *
+	 * @param string sLabel
+	 * @param object oObject
+	 */
+	public function AddResultRow($sLabel, $aRow)
+	{
+		$this->m_aResult[$sLabel] = $aRow;
+	}
+
+	/**
 	 * Log an error
 	 *
 	 * @param string sDescription
@@ -236,8 +247,18 @@ class WebServiceResultFailedLogin extends WebServiceResult
  *
  * @package     iTopORM
  */
-class WebServices
+abstract class WebServicesBase
 {
+	static public function GetWSDLContents($sServiceCategory = '')
+	{
+		if ($sServiceCategory == '')
+		{
+			$sServiceCategory = 'BasicServices';
+		}
+		$sWsdlFilePath = call_user_func(array($sServiceCategory, 'GetWSDLFilePath'));
+		return file_get_contents($sWsdlFilePath);
+	}
+
 	/**
 	 * Helper to log a service delivery
 	 *
@@ -537,138 +558,21 @@ class WebServices
 		return $aRes;
 	}
 
-
-	/**
-	 * Get the server version (TODO: get it dynamically, where ?)
-	 *	 
-	 * @return WebServiceResult
-	 */
-	static public function GetVersion()
+	static protected function SoapStructToAssociativeArray($aArrayOfAssocArray)
 	{
-		if (ITOP_REVISION == '$WCREV$')
-		{
-			$sVersionString = ITOP_VERSION.' [dev]';
-		}
-		else
-		{
-			// This is a build made from SVN, let display the full information
-			$sVersionString = ITOP_VERSION."-".ITOP_REVISION." ".ITOP_BUILD_DATE;
-		}
-
-		return $sVersionString;
-	}
+		if (is_null($aArrayOfAssocArray)) return array();
 
-	public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency)
-	{
-		if (!UserRights::CheckCredentials($sLogin, $sPassword))
-		{
-			$oRes = new WebServiceResultFailedLogin($sLogin);
-			$this->LogUsage(__FUNCTION__, $oRes);
-
-			return $oRes->ToSoapStructure();
-		}
-		UserRights::Login($sLogin);
-
-		$aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc);
-		$aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc);
-		$aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc);
-		$aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc);
-		$aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc);
-
-		$aImpactedCIs = array();
-		if (is_null($aSOAPImpactedCIs)) $aSOAPImpactedCIs = array();
-		foreach($aSOAPImpactedCIs as $oImpactedCIs)
-		{
-			$aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs);
-		}
-
-		$oRes = $this->_CreateIncidentTicket
-		(
-			$sTitle,
-			$sDescription,
-			$aCallerDesc,
-			$aCustomerDesc,
-			$aServiceDesc,
-			$aServiceSubcategoryDesc,
-			$sProduct,
-			$aWorkgroupDesc,
-			$aImpactedCIs,
-			$sImpact,
-			$sUrgency
-		);
-		return $oRes->ToSoapStructure();
-	}
-
-	/**
-	 * Create an incident ticket from a monitoring system
-	 * Some CIs might be specified (by their name/IP)
-	 *	 
-	 * @param string sTitle
-	 * @param string sDescription
-	 * @param array aCallerDesc
-	 * @param array aCustomerDesc
-	 * @param array aServiceDesc
-	 * @param array aServiceSubcategoryDesc
-	 * @param string sProduct
-	 * @param array aWorkgroupDesc
-	 * @param array aImpactedCIs
-	 * @param string sImpact
-	 * @param string sUrgency
-	 *
-	 * @return WebServiceResult
-	 */
-	protected function _CreateIncidentTicket($sTitle, $sDescription, $aCallerDesc, $aCustomerDesc, $aServiceDesc, $aServiceSubcategoryDesc, $sProduct, $aWorkgroupDesc, $aImpactedCIs, $sImpact, $sUrgency)
-	{
-
-		$oRes = new WebServiceResult();
-
-		try
+		$aRes = array();
+		foreach($aArrayOfAssocArray as $aAssocArray)
 		{
-			$oMyChange = MetaModel::NewObject("CMDBChange");
-			$oMyChange->Set("date", time());
-			$oMyChange->Set("userinfo", "Administrator");
-			$iChangeId = $oMyChange->DBInsertNoReload();
-	
-			$oNewTicket = MetaModel::NewObject('Incident');
-			$this->MyObjectSetScalar('title', 'title', $sTitle, $oNewTicket, $oRes);
-			$this->MyObjectSetScalar('description', 'description', $sDescription, $oNewTicket, $oRes);
-
-			$this->MyObjectSetExternalKey('org_id', 'customer', $aCustomerDesc, $oNewTicket, $oRes);
-			$this->MyObjectSetExternalKey('caller_id', 'caller', $aCallerDesc, $oNewTicket, $oRes);
-	
-			$this->MyObjectSetExternalKey('service_id', 'service', $aServiceDesc, $oNewTicket, $oRes);
-			$this->MyObjectSetExternalKey('servicesubcategory_id', 'servicesubcategory', $aServiceSubcategoryDesc, $oNewTicket, $oRes);
-			$this->MyObjectSetScalar('product', 'product', $sProduct, $oNewTicket, $oRes);
-
-			$this->MyObjectSetExternalKey('workgroup_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes);
-
-
-			$aDevicesNotFound = $this->AddLinkedObjects('ci_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes);
-			if (count($aDevicesNotFound) > 0)
+			$aRow = array();
+			foreach ($aAssocArray as $oKeyValuePair)
 			{
-				$this->MyObjectSetScalar('description', 'n/a', $sDescription.' - Related CIs: '.implode(', ', $aDevicesNotFound), $oNewTicket, $oRes);
+				$aRow[$oKeyValuePair->key] = $oKeyValuePair->value;
 			}
-			else
-			{
-				$this->MyObjectSetScalar('description', 'n/a', $sDescription, $oNewTicket, $oRes);
-			}
-
-			$this->MyObjectSetScalar('impact', 'impact', $sImpact, $oNewTicket, $oRes);
-			$this->MyObjectSetScalar('urgency', 'urgency', $sUrgency, $oNewTicket, $oRes);
-
-			$this->MyObjectInsert($oNewTicket, 'created', $oMyChange, $oRes);
-		}
-		catch (CoreException $e)
-		{
-			$oRes->LogError($e->getMessage());
+			$aRes[] = $aRow;		
 		}
-		catch (Exception $e)
-		{
-			$oRes->LogError($e->getMessage());
-		}
-
-		$this->LogUsage(__FUNCTION__, $oRes);
-		return $oRes;
+		return $aRes;
 	}
 }
 ?>