diff --git a/components/ILIAS/Mail/classes/class.ilMailLuceneSearcher.php b/components/ILIAS/Mail/classes/class.ilMailLuceneSearcher.php index 4d5f687a00e5..301efb7ea3ea 100755 --- a/components/ILIAS/Mail/classes/class.ilMailLuceneSearcher.php +++ b/components/ILIAS/Mail/classes/class.ilMailLuceneSearcher.php @@ -35,7 +35,12 @@ public function search(int $user_id, int $mail_folder_id): void } try { - $xml = ilRpcClientFactory::factory('RPCSearchHandler')->searchMail(); + $xml = ilRpcClientFactory::factory('RPCSearchHandler')->searchMail( + CLIENT_ID . '_' . $this->settings->get('inst_id', '0'), + $user_id, + $this->query_parser->getQuery(), + $mail_folder_id + ); } catch (Exception $e) { ilLoggerFactory::getLogger('mail')->critical($e->getMessage()); throw $e; diff --git a/components/ILIAS/WebServices/RPC/classes/class.ilRpcClient.php b/components/ILIAS/WebServices/RPC/classes/class.ilRpcClient.php index c0c9bae7e5ff..3b91331c007c 100755 --- a/components/ILIAS/WebServices/RPC/classes/class.ilRpcClient.php +++ b/components/ILIAS/WebServices/RPC/classes/class.ilRpcClient.php @@ -19,15 +19,12 @@ declare(strict_types=1); /** - * Class ilRpcClient - * * @author Fabian Wolf - * @ingroup ServicesWebServicesRPC * * List of all known RPC methods... * * RPCIndexHandler: - * @method void index() index(string $client, bool $bool) Prefix/Package: RPCIndexHandler + * @method bool index() index(string $client, bool $bool) Prefix/Package: RPCIndexHandler * @method void indexObjects() indexObjects(string $client, array $object_ids) Prefix/Package: RPCIndexHandler * * RPCTransformationHandler: @@ -48,56 +45,38 @@ class ilRpcClient protected string $url; protected string $prefix = ''; protected int $timeout = 0; - protected string $encoding = ''; protected ilLogger $logger; /** - * ilRpcClient constructor. - * @param string $a_url URL to connect to - * @param string $a_prefix Optional prefix for method names - * @param int $a_timeout The maximum number of seconds to allow ilRpcClient to connect. - * @param string $a_encoding Character encoding + * @param string $url URL to connect to + * @param string $prefix Optional prefix for method names + * @param int $timeout The maximum number of seconds to allow ilRpcClient to connect. * @throws ilRpcClientException */ - public function __construct(string $a_url, string $a_prefix = '', int $a_timeout = 0, string $a_encoding = 'utf-8') + public function __construct(string $url, string $prefix = '', int $timeout = 0) { global $DIC; $this->logger = $DIC->logger()->wsrv(); - - if (!extension_loaded('xmlrpc')) { - ilLoggerFactory::getLogger('wsrv')->error('RpcClient Xmlrpc extension not enabled'); - throw new ilRpcClientException('Xmlrpc extension not enabled.', 50); - } - - $this->url = $a_url; - $this->prefix = $a_prefix; - $this->timeout = $a_timeout; - $this->encoding = $a_encoding; + $this->url = $url; + $this->prefix = $prefix; + $this->timeout = $timeout; } /** - * Magic caller to all RPC functions - * - * @param string $a_method Method name - * @param array $a_params Argument array - * @return mixed Returns either an array, or an integer, or a string, or a boolean according to the response returned by the XMLRPC method. + * @param string $method Method name + * @param (string|int|bool|int[])[] $parameters Argument array + * @return string|stdClass Depends on the response returned by the XMLRPC method. * @throws ilRpcClientException */ - public function __call(string $a_method, array $a_params) + public function __call(string $method, array $parameters): string|bool|stdClass { //prepare xml post data - $method_name = str_replace('_', '.', $this->prefix . $a_method); - $rpc_options = array( - 'verbosity' => 'newlines_only', - 'escaping' => 'markup' - ); + $method_name = str_replace('_', '.', $this->prefix . $method); + + $post_data = $this->encodeRequest($method_name, $parameters); - if ($this->encoding) { - $rpc_options['encoding'] = $this->encoding; - } - $post_data = xmlrpc_encode_request($method_name, $a_params, $rpc_options); //try to connect to the given url try { $curl = new ilCurlConnection($this->url); @@ -111,7 +90,7 @@ public function __call(string $a_method, array $a_params) $curl->setOpt(CURLOPT_TIMEOUT, $this->timeout); } $this->logger->debug('RpcClient request to ' . $this->url . ' / ' . $method_name); - $xml_resp = $curl->exec(); + $xml_response = $curl->exec(); } catch (ilCurlConnectionException $e) { $this->logger->error( 'RpcClient could not connect to ' . $this->url . ' ' . @@ -120,18 +99,200 @@ public function __call(string $a_method, array $a_params) throw new ilRpcClientException($e->getMessage(), $e->getCode()); } - //prepare output, throw exception if rpc fault is detected - $resp = xmlrpc_decode($xml_resp, $this->encoding); + //return output, throw exception if rpc fault is detected + return $this->handleResponse($xml_response); + } - //xmlrpc_is_fault can just handle arrays as response - if (is_array($resp) && xmlrpc_is_fault($resp)) { - $this->logger->error('RpcClient recieved error ' . $resp['faultCode'] . ': ' . $resp['faultString']); - throw new ilRpcClientException( - 'RPC-Server returned fault message: ' . - $resp['faultString'], - $resp['faultCode'] - ); + /** + * @param (string|int|bool|int[])[] $parameters + * @throws ilRpcClientException + */ + protected function encodeRequest(string $method, array $parameters): string + { + $xml = new DOMDocument('1.0', 'UTF-8'); + $method_call = $xml->createElement('methodCall'); + $method_name = $xml->createElement('methodName', $method); + $params = $xml->createElement('params'); + + foreach ($parameters as $parameter) { + match (true) { + is_string($parameter) => $encoded_parameter = $this->encodeString($parameter), + is_int($parameter) => $encoded_parameter = $this->encodeInteger($parameter), + is_bool($parameter) => $encoded_parameter = $this->encodeBoolean($parameter), + $this->isListOfIntegers($parameter) => $encoded_parameter = $this->encodeListOfIntegers(...$parameter), + default => throw new ilRpcClientException( + 'Invalid parameter type, only string, int, bool, and int[] are supported.' + ) + }; + $params->appendChild($xml->importNode($this->wrapParameter($encoded_parameter)->documentElement, true)); + } + + $method_call->appendChild($method_name); + $method_call->appendChild($params); + + $xml->appendChild($method_call); + return $xml->saveXML(); + } + + protected function isListOfIntegers(mixed $parameter): bool + { + if (!is_array($parameter)) { + return false; + } + foreach ($parameter as $entries) { + if (!is_int($entries)) { + return false; + } + } + return true; + } + + protected function wrapParameter(DOMDocument $encoded_parameter): DOMDocument + { + $xml = new DOMDocument('1.0', 'UTF-8'); + $param = $xml->createElement('param'); + $value = $xml->createElement('value'); + + $value->appendChild($xml->importNode($encoded_parameter->documentElement, true)); + $param->appendChild($value); + + $xml->appendChild($param); + return $xml; + } + + protected function encodeString(string $parameter): DOMDocument + { + $xml = new DOMDocument('1.0', 'UTF-8'); + $xml->appendChild($xml->createElement('string', $parameter)); + return $xml; + } + + protected function encodeInteger(int $parameter): DOMDocument + { + $xml = new DOMDocument('1.0', 'UTF-8'); + $xml->appendChild($xml->createElement('int', (string) $parameter)); + return $xml; + } + + protected function encodeBoolean(bool $parameter): DOMDocument + { + $xml = new DOMDocument('1.0', 'UTF-8'); + $xml->appendChild($xml->createElement('boolean', $parameter ? '1' : '0')); + return $xml; + } + + protected function encodeListOfIntegers(int ...$parameters): DOMDocument + { + $xml = new DOMDocument('1.0', 'UTF-8'); + $array = $xml->createElement('array'); + $data = $xml->createElement('data'); + + foreach ($parameters as $parameter) { + $value = $xml->createElement('value'); + $value->appendChild($xml->importNode($this->encodeInteger($parameter)->documentElement, true)); + $data->appendChild($value); + } + $array->appendChild($data); + + $xml->appendChild($array); + return $xml; + } + + /** + * Returns decoded response if not faulty, otherwise throws exception. + * @throws ilRpcClientException + */ + public function handleResponse(string $xml): string|bool|stdClass + { + $response = new DOMDocument('1.0', 'UTF-8'); + $response->preserveWhiteSpace = false; + $response->loadXML($xml); + + if (!$response) { + throw new ilRpcClientException('Invalid XML response'); + } + + $response_body = $response->documentElement->childNodes->item(0); + + if ($response_body === null) { + throw new ilRpcClientException('Empty response'); + } + + $this->logger->dump($response_body); + + return match ($response_body->nodeName) { + 'params' => $this->decodeOKResponse($response_body), + 'fault' => $this->handleFaultResponse($response_body), + default => throw new ilRpcClientException('Unexpected element in response: ' . get_class($response_body)), + }; + } + + protected function decodeOKResponse(DOMElement $response_body): string|bool|stdClass + { + $param_child = $response_body->getElementsByTagName('value')->item(0)?->childNodes?->item(0); + + if ($param_child === null) { + throw new ilRpcClientException('No value in response'); + } + + return match ($param_child->nodeName) { + 'string' => $this->decodeString($param_child), + '#text' => $this->decodeString($param_child), // org.apache.xmlrpc returns java strings as unwrapped text node + 'base64' => $this->decodeBase64($param_child), + 'boolean' => $this->decodeBoolean($param_child), + default => throw new ilRpcClientException('Unexpected element in response value: ' . $param_child->nodeName), + }; + } + + protected function decodeString(DOMNode $string): string + { + return (string) $string->nodeValue; + } + + protected function decodeBase64(DOMNode $base64): stdClass + { + return (object) base64_decode((string) $base64->nodeValue); + } + + protected function decodeBoolean(DOMNode $boolean): bool + { + return (bool) $boolean->nodeValue; + } + + /** + * @throws ilRpcClientException + */ + protected function handleFaultResponse(DOMElement $response_body): string + { + $fault_code = null; + $fault_string = null; + + $members = $response_body->getElementsByTagName('member'); + foreach ($members as $member) { + $name = $member->getElementsByTagName('name')->item(0)?->nodeValue; + if ($name === 'faultCode') { + if ($fault_code !== null) { + throw new ilRpcClientException('Multiple codes in fault response.'); + } + $fault_code = (int) $member->getElementsByTagName('int')->item(0)?->nodeValue; + } + if ($name === 'faultString') { + if ($fault_string !== null) { + throw new ilRpcClientException('Multiple strings in fault response.'); + } + $fault_string = $member->getElementsByTagName('string')->item(0)?->nodeValue; + } + } + + if ($fault_code === null || $fault_string === null) { + throw new ilRpcClientException('No code or no string in fault respsonse'); } - return $resp; + + $this->logger->error('RpcClient recieved error ' . $fault_code . ': ' . $fault_string); + throw new ilRpcClientException( + 'RPC-Server returned fault message: ' . + $fault_string, + $fault_code + ); } } diff --git a/components/ILIAS/WebServices/RPC/classes/class.ilRpcClientFactory.php b/components/ILIAS/WebServices/RPC/classes/class.ilRpcClientFactory.php index 7f623b17b515..d09ea25de439 100755 --- a/components/ILIAS/WebServices/RPC/classes/class.ilRpcClientFactory.php +++ b/components/ILIAS/WebServices/RPC/classes/class.ilRpcClientFactory.php @@ -17,26 +17,22 @@ *********************************************************************/ declare(strict_types=1); + /** - * @classDescription Factory for ILIAS rpc client * @author Stefan Meyer */ class ilRpcClientFactory { /** - * Creates an ilRpcClient instance to our ilServer - * - * @param string $a_package Package name - * @param int $a_timeout The maximum number of seconds to allow ilRpcClient to connect. - * @return ilRpcClient + * @param string $package Package name + * @param int $timeout The maximum number of seconds to allow ilRpcClient to connect. */ - public static function factory(string $a_package, int $a_timeout = 0): ilRpcClient + public static function factory(string $package, int $timeout = 0): ilRpcClient { return new ilRpcClient( ilRPCServerSettings::getInstance()->getServerUrl(), - $a_package . '.', - $a_timeout, - 'UTF-8' + $package . '.', + $timeout ); } }