From fcbd9b8911f788906e4fa9e39b940e7530e9edec Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 23 Nov 2010 16:16:01 +0000 Subject: [PATCH] Improved Web services: opened to services coming from an optional module SVN:trunk[965] --- core/config.class.inc.php | 20 +++ core/dbobjectset.class.php | 24 +++ core/metamodel.class.php | 4 + setup/index.php | 25 ++- setup/setuppage.class.inc.php | 11 +- test/testlist.inc.php | 228 +++++++++++++++++++++--- webservices/itop.wsdl.php | 50 +++++- webservices/itop.wsdl.tpl | 24 +++ webservices/itopsoap.examples.php | 51 ++++++ webservices/itopsoaptypes.class.inc.php | 48 +++-- webservices/soapserver.php | 46 ++++- webservices/webservices.basic.php | 215 ++++++++++++++++++++++ webservices/webservices.class.inc.php | 162 ++++------------- 13 files changed, 722 insertions(+), 186 deletions(-) create mode 100644 webservices/webservices.basic.php diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 4a113be59..5b04fe61d 100644 --- a/core/config.class.inc.php +++ b/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) { diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 979826c5b..a55e1172f 100644 --- a/core/dbobjectset.class.php +++ b/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(); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index c266b0d64..26c8f6089 100644 --- a/core/metamodel.class.php +++ b/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); diff --git a/setup/index.php b/setup/index.php index 226238472..152dcc05c 100644 --- a/setup/index.php +++ b/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); } diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php index 7834c5549..3e62f2764 100644 --- a/setup/setuppage.class.inc.php +++ b/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; + } } } } diff --git a/test/testlist.inc.php b/test/testlist.inc.php index d54682ec1..a15ac5992 100644 --- a/test/testlist.inc.php +++ b/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 "

Note: You may also want to try the sample SOAP client itopsoap.examples.php

\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 "

SOAP call #$iPos - {$aWebService['verb']}

\n"; echo "

{$aWebService['explain result']}

\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 "

SOAP call #$iPos - {$aWebService['verb']}

\n"; echo "

{$aWebService['explain result']}

\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';} diff --git a/webservices/itop.wsdl.php b/webservices/itop.wsdl.php index d36e227e3..aa71bb405 100644 --- a/webservices/itop.wsdl.php +++ b/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.... +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'); + +// Load the modules installed and enabled // -header('Content-Type: application/xml; charset=UTF-8'); -//header('Content-Disposition: attachment; filename="itop.wsdl"'); -header('Content-Disposition: online; filename="itop.wsdl"'); +$oConfig = new Config(APPROOT.'config-itop.php'); +$aFiles = $oConfig->GetWebServiceCategories(); +foreach ($aFiles as $sFile) +{ + require_once(APPROOT.$sFile); +} -$sMyWsdl = './itop.wsdl.tpl'; - -$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___', diff --git a/webservices/itop.wsdl.tpl b/webservices/itop.wsdl.tpl index f4a2b4051..8d6ddf6ca 100644 --- a/webservices/itop.wsdl.tpl +++ b/webservices/itop.wsdl.tpl @@ -161,6 +161,14 @@ + + + + + + + + @@ -177,6 +185,13 @@ + + + Create a ticket, return information about reconciliation on external keys and the created ticket + --> + + + @@ -198,6 +213,15 @@ + + + + + + + + + diff --git a/webservices/itopsoap.examples.php b/webservices/itopsoap.examples.php index 402ac0f27..08e191e35 100644 --- a/webservices/itopsoap.examples.php +++ b/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 "\n"; echo "

\n"; + + $oRes = $oSoapClient->SearchObjects + ( + 'admin', /* login */ + 'admin', /* password */ + 'SELECT URP_Profiles' /* oql */ + ); + + echo "

SearchObjects() returned:\n"; + if ($oRes->status) + { + $aResults = $oRes->result; + + echo "\n"; + + // Header made after the first line + echo "\n"; + foreach ($aResults[0]->values as $aKeyValuePair) + { + echo " \n"; + } + echo "\n"; + + foreach ($aResults as $iRow => $aData) + { + echo "\n"; + foreach ($aData->values as $aKeyValuePair) + { + echo " \n"; + } + echo "\n"; + } + echo "
".$aKeyValuePair->key."
".$aKeyValuePair->value."
\n"; + } + else + { + $aErrors = array(); + foreach ($oRes->errors->messages as $oMessage) + { + $aErrors[] = $oMessage->text; + } + $sErrorMsg = implode(', ', $aErrors); + echo "

SearchObjects() failed with message: $sErrorMsg

\n"; + //echo "
\n";
+		//print_r($oRes);
+		//echo "
\n"; + } + echo "

\n"; } catch(SoapFault $e) { diff --git a/webservices/itopsoaptypes.class.inc.php b/webservices/itopsoaptypes.class.inc.php index c55a8283f..5811236a3 100644 --- a/webservices/itopsoaptypes.class.inc.php +++ b/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; + } +} ?> diff --git a/webservices/soapserver.php b/webservices/soapserver.php index fb7515924..28d67c794 100644 --- a/webservices/soapserver.php +++ b/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 "
    \n"; foreach($aFunctions as $sFunc) { + if ($sFunc == 'GetWSDLContents') continue; + echo "
  • $sFunc
  • \n"; } echo "
\n"; echo "

Here the WSDL file

"; + + echo "You may also want to try the following service categories: "; + echo "

    \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 "
  • $sServiceCategory
  • \n"; + } + } + echo "
\n"; } ?> diff --git a/webservices/webservices.basic.php b/webservices/webservices.basic.php new file mode 100644 index 000000000..c40e09c4d --- /dev/null +++ b/webservices/webservices.basic.php @@ -0,0 +1,215 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @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; + } +} +?> diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index 8575b33b5..e55b72b94 100644 --- a/webservices/webservices.class.inc.php +++ b/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); } @@ -140,6 +140,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 * @@ -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$') + if (is_null($aArrayOfAssocArray)) return array(); + + $aRes = array(); + foreach($aArrayOfAssocArray as $aAssocArray) { - $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) + $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); + $aRes[] = $aRow; } - catch (CoreException $e) - { - $oRes->LogError($e->getMessage()); - } - catch (Exception $e) - { - $oRes->LogError($e->getMessage()); - } - - $this->LogUsage(__FUNCTION__, $oRes); - return $oRes; + return $aRes; } } ?>