Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion components/ILIAS/Mail/classes/class.ilMailLuceneSearcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
257 changes: 209 additions & 48 deletions components/ILIAS/WebServices/RPC/classes/class.ilRpcClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,12 @@
declare(strict_types=1);

/**
* Class ilRpcClient
*
* @author Fabian Wolf <wolf@leifos.com>
* @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:
Expand All @@ -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);
Expand All @@ -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 . ' ' .
Expand All @@ -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
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,22 @@
*********************************************************************/

declare(strict_types=1);

/**
* @classDescription Factory for ILIAS rpc client
* @author Stefan Meyer <meyer@leifos.com>
*/
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
);
}
}
Loading