├── COPYRIGHT.md
├── src
├── Exception
│ ├── ExceptionInterface.php
│ ├── RuntimeException.php
│ ├── BadMethodCallException.php
│ ├── InvalidArgumentException.php
│ └── ValueException.php
├── Client
│ ├── Exception
│ │ ├── RuntimeException.php
│ │ ├── InvalidArgumentException.php
│ │ ├── HttpException.php
│ │ ├── IntrospectException.php
│ │ ├── ExceptionInterface.php
│ │ └── FaultException.php
│ ├── ServerProxy.php
│ └── ServerIntrospection.php
├── Server
│ ├── Exception
│ │ ├── RuntimeException.php
│ │ ├── BadMethodCallException.php
│ │ ├── InvalidArgumentException.php
│ │ └── ExceptionInterface.php
│ ├── Cache.php
│ ├── System.php
│ └── Fault.php
├── Value
│ ├── Nil.php
│ ├── BigInteger.php
│ ├── AbstractScalar.php
│ ├── Text.php
│ ├── Double.php
│ ├── Integer.php
│ ├── Boolean.php
│ ├── ArrayValue.php
│ ├── Base64.php
│ ├── Struct.php
│ ├── AbstractCollection.php
│ └── DateTime.php
├── Response
│ └── Http.php
├── Generator
│ ├── GeneratorInterface.php
│ ├── XmlWriter.php
│ ├── DomDocument.php
│ └── AbstractGenerator.php
├── Request
│ ├── Stdin.php
│ └── Http.php
├── Response.php
├── Fault.php
├── Client.php
├── Request.php
├── AbstractValue.php
└── Server.php
├── LICENSE.md
├── composer.json
└── README.md
/COPYRIGHT.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/)
2 |
--------------------------------------------------------------------------------
/src/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | type = self::XMLRPC_TYPE_NIL;
13 | $this->value = null;
14 | }
15 |
16 | /**
17 | * Return the value of this object, convert the XML-RPC native nill value into a PHP NULL
18 | */
19 | public function getValue(): void
20 | {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Value/BigInteger.php:
--------------------------------------------------------------------------------
1 | value = (string) BigIntegerMath::of($value);
15 | $this->type = self::XMLRPC_TYPE_I8;
16 | }
17 |
18 | /**
19 | * Return bigint value object
20 | *
21 | * @return string
22 | */
23 | public function getValue()
24 | {
25 | return $this->value;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Value/AbstractScalar.php:
--------------------------------------------------------------------------------
1 | openElement('value')
20 | ->openElement($this->type, $this->value)
21 | ->closeElement($this->type)
22 | ->closeElement('value');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Response/Http.php:
--------------------------------------------------------------------------------
1 | getEncoding()));
23 | }
24 |
25 | return parent::__toString();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Value/Text.php:
--------------------------------------------------------------------------------
1 | type = self::XMLRPC_TYPE_STRING;
15 |
16 | // Make sure this value is string and all XML characters are encoded
17 | $this->value = (string) $value;
18 | }
19 |
20 | /**
21 | * Return the value of this object, convert the XML-RPC native string value into a PHP string
22 | *
23 | * @return string
24 | */
25 | public function getValue()
26 | {
27 | return (string) $this->value;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Value/Double.php:
--------------------------------------------------------------------------------
1 | type = self::XMLRPC_TYPE_DOUBLE;
19 | $precision = (int) ini_get('precision');
20 | $formatString = '%1.' . $precision . 'F';
21 | $this->value = rtrim(sprintf($formatString, (float) $value), '0');
22 | }
23 |
24 | /**
25 | * Return the value of this object, convert the XML-RPC native double value into a PHP float
26 | *
27 | * @return float
28 | */
29 | public function getValue()
30 | {
31 | return (float) $this->value;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Value/Integer.php:
--------------------------------------------------------------------------------
1 | PHP_INT_MAX) {
20 | throw new Exception\ValueException('Overlong integer given');
21 | }
22 |
23 | $this->type = self::XMLRPC_TYPE_INTEGER;
24 | $this->value = (int) $value; // Make sure this value is integer
25 | }
26 |
27 | /**
28 | * Return the value of this object, convert the XML-RPC native integer value into a PHP integer
29 | *
30 | * @return int
31 | */
32 | public function getValue()
33 | {
34 | return $this->value;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Generator/GeneratorInterface.php:
--------------------------------------------------------------------------------
1 | type = self::XMLRPC_TYPE_BOOLEAN;
20 | // Make sure the value is boolean and then convert it into an integer
21 | // The double conversion is because a bug in the LaminasOptimizer in PHP version 5.0.4
22 | $this->value = (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
23 | }
24 |
25 | /**
26 | * Return the value of this object, convert the XML-RPC native boolean value into a PHP boolean
27 | *
28 | * @return bool
29 | */
30 | public function getValue()
31 | {
32 | return (bool) $this->value;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Value/ArrayValue.php:
--------------------------------------------------------------------------------
1 | type = self::XMLRPC_TYPE_ARRAY;
17 | parent::__construct($value);
18 | }
19 |
20 | /**
21 | * Generate the XML code that represent an array native MXL-RPC value
22 | *
23 | * @return void
24 | */
25 | protected function generate()
26 | {
27 | $generator = static::getGenerator();
28 | $generator
29 | ->openElement('value')
30 | ->openElement('array')
31 | ->openElement('data');
32 |
33 | if (is_array($this->value)) {
34 | foreach ($this->value as $val) {
35 | $val->generateXml();
36 | }
37 | }
38 |
39 | $generator
40 | ->closeElement('data')
41 | ->closeElement('array')
42 | ->closeElement('value');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Value/Base64.php:
--------------------------------------------------------------------------------
1 | type = self::XMLRPC_TYPE_BASE64;
20 |
21 | $value = (string) $value; // Make sure this value is string
22 | if (! $alreadyEncoded) {
23 | $value = base64_encode($value); // We encode it in base64
24 | }
25 | $this->value = $value;
26 | }
27 |
28 | /**
29 | * Return the value of this object, convert the XML-RPC native base64 value into a PHP string
30 | * We return this value decoded (a normal string)
31 | *
32 | * @return string
33 | */
34 | public function getValue()
35 | {
36 | return base64_decode($this->value);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Value/Struct.php:
--------------------------------------------------------------------------------
1 | type = self::XMLRPC_TYPE_STRUCT;
17 | parent::__construct($value);
18 | }
19 |
20 | /**
21 | * Generate the XML code that represent struct native MXL-RPC value
22 | *
23 | * @return void
24 | */
25 | protected function generate()
26 | {
27 | $generator = static::getGenerator();
28 | $generator
29 | ->openElement('value')
30 | ->openElement('struct');
31 |
32 | if (is_array($this->value)) {
33 | foreach ($this->value as $name => $val) {
34 | $generator
35 | ->openElement('member')
36 | ->openElement('name', $name)
37 | ->closeElement('name');
38 | $val->generateXml();
39 | $generator->closeElement('member');
40 | }
41 | }
42 |
43 | $generator
44 | ->closeElement('struct')
45 | ->closeElement('value');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Value/AbstractCollection.php:
--------------------------------------------------------------------------------
1 | $value) {
18 | // If the elements of the given array are not Laminas\XmlRpc\Value objects,
19 | // we need to convert them as such (using auto-detection from PHP value)
20 | if (! $value instanceof parent) {
21 | $value = static::getXmlRpcValue($value, self::AUTO_DETECT_TYPE);
22 | }
23 | $this->value[$key] = $value;
24 | }
25 | }
26 |
27 | /**
28 | * Return the value of this object, convert the XML-RPC native collection values into a PHP array
29 | *
30 | * @return array
31 | */
32 | public function getValue()
33 | {
34 | $values = (array) $this->value;
35 | foreach ($values as $key => $value) {
36 | $values[$key] = $value->getValue();
37 | }
38 | return $values;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | - Redistributions of source code must retain the above copyright notice, this
7 | list of conditions and the following disclaimer.
8 |
9 | - Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | - Neither the name of Laminas Foundation nor the names of its contributors may
14 | be used to endorse or promote products derived from this software without
15 | specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/src/Request/Stdin.php:
--------------------------------------------------------------------------------
1 | fault = new Fault(630);
41 | return;
42 | }
43 |
44 | $xml = '';
45 | while (! feof($fh)) {
46 | $xml .= fgets($fh);
47 | }
48 | fclose($fh);
49 |
50 | $this->xml = $xml;
51 |
52 | $this->loadXml($xml);
53 | }
54 |
55 | /**
56 | * Retrieve the raw XML request
57 | *
58 | * @return string
59 | */
60 | public function getRawRequest()
61 | {
62 | return $this->xml;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Client/ServerProxy.php:
--------------------------------------------------------------------------------
1 | foo->bar->baz()".
13 | */
14 | class ServerProxy
15 | {
16 | private XMLRPCClient $client;
17 |
18 | private string $namespace = '';
19 |
20 | /** @var array of \Laminas\XmlRpc\Client\ServerProxy */
21 | private array $cache = [];
22 |
23 | /**
24 | * @param string $namespace
25 | */
26 | public function __construct(XMLRPCClient $client, $namespace = '')
27 | {
28 | $this->client = $client;
29 | $this->namespace = $namespace;
30 | }
31 |
32 | /**
33 | * Get the next successive namespace
34 | *
35 | * @param string $namespace
36 | * @return ServerProxy
37 | */
38 | public function __get($namespace)
39 | {
40 | $namespace = ltrim("$this->namespace.$namespace", '.');
41 | if (! isset($this->cache[$namespace])) {
42 | $this->cache[$namespace] = new $this($this->client, $namespace);
43 | }
44 | return $this->cache[$namespace];
45 | }
46 |
47 | /**
48 | * Call a method in this namespace.
49 | *
50 | * @param string $method
51 | * @param array $args
52 | * @return mixed
53 | */
54 | public function __call($method, $args)
55 | {
56 | $method = ltrim("{$this->namespace}.{$method}", '.');
57 | return $this->client->call($method, $args);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Generator/XmlWriter.php:
--------------------------------------------------------------------------------
1 | xmlWriter = new \XMLWriter();
25 | $this->xmlWriter->openMemory();
26 | $this->xmlWriter->startDocument('1.0', $this->encoding);
27 | }
28 |
29 | /**
30 | * Open a new XML element
31 | *
32 | * @param string $name XML element name
33 | * @return void
34 | */
35 | protected function openXmlElement($name)
36 | {
37 | $this->xmlWriter->startElement($name);
38 | }
39 |
40 | /**
41 | * Write XML text data into the currently opened XML element
42 | *
43 | * @param string $text XML text data
44 | * @return void
45 | */
46 | protected function writeTextData($text)
47 | {
48 | $this->xmlWriter->text($text);
49 | }
50 |
51 | /**
52 | * Close a previously opened XML element
53 | *
54 | * @param string $name
55 | * @return XmlWriter
56 | */
57 | protected function closeXmlElement($name)
58 | {
59 | $this->xmlWriter->endElement();
60 |
61 | return $this;
62 | }
63 |
64 | /**
65 | * Emit XML document
66 | *
67 | * @return string
68 | */
69 | public function saveXml()
70 | {
71 | return $this->xmlWriter->flush(false);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Generator/DomDocument.php:
--------------------------------------------------------------------------------
1 | dom->createElement($name);
27 |
28 | $this->currentElement = $this->currentElement->appendChild($newElement);
29 | }
30 |
31 | /**
32 | * Write XML text data into the currently opened XML element
33 | *
34 | * @param string $text
35 | */
36 | protected function writeTextData($text)
37 | {
38 | $this->currentElement->appendChild($this->dom->createTextNode($text));
39 | }
40 |
41 | /**
42 | * Close a previously opened XML element
43 | *
44 | * Resets $currentElement to the next parent node in the hierarchy
45 | *
46 | * @param string $name
47 | * @return void
48 | */
49 | protected function closeXmlElement($name)
50 | {
51 | if (isset($this->currentElement->parentNode)) {
52 | $this->currentElement = $this->currentElement->parentNode;
53 | }
54 | }
55 |
56 | /**
57 | * Save XML as a string
58 | *
59 | * @return string
60 | */
61 | public function saveXml()
62 | {
63 | return $this->dom->saveXml();
64 | }
65 |
66 | /**
67 | * Initializes internal objects
68 | *
69 | * @return void
70 | */
71 | protected function init()
72 | {
73 | $this->dom = new \DOMDocument('1.0', $this->encoding);
74 | $this->currentElement = $this->dom;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Value/DateTime.php:
--------------------------------------------------------------------------------
1 | type = self::XMLRPC_TYPE_DATETIME;
38 |
39 | if ($value instanceof \DateTime) {
40 | $this->value = $value->format($this->phpFormatString);
41 | } elseif (is_numeric($value)) { // The value is numeric, we make sure it is an integer
42 | $this->value = date($this->phpFormatString, (int) $value);
43 | } else {
44 | try {
45 | $dateTime = new \DateTime($value);
46 | } catch (\Exception $e) {
47 | throw new Exception\ValueException($e->getMessage(), $e->getCode(), $e);
48 | }
49 |
50 | $this->value = $dateTime->format($this->phpFormatString); // Convert the DateTime to iso8601 format
51 | }
52 | }
53 |
54 | /**
55 | * Return the value of this object as iso8601 dateTime value
56 | *
57 | * @return string Formatted datetime
58 | */
59 | public function getValue()
60 | {
61 | return $this->value;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laminas/laminas-xmlrpc",
3 | "description": "Fully-featured XML-RPC server and client implementations",
4 | "license": "BSD-3-Clause",
5 | "keywords": [
6 | "laminas",
7 | "xmlrpc"
8 | ],
9 | "homepage": "https://laminas.dev",
10 | "support": {
11 | "docs": "https://docs.laminas.dev/laminas-xmlrpc/",
12 | "issues": "https://github.com/laminas/laminas-xmlrpc/issues",
13 | "source": "https://github.com/laminas/laminas-xmlrpc",
14 | "rss": "https://github.com/laminas/laminas-xmlrpc/releases.atom",
15 | "chat": "https://laminas.dev/chat",
16 | "forum": "https://discourse.laminas.dev"
17 | },
18 | "config": {
19 | "sort-packages": true,
20 | "platform": {
21 | "php": "8.1.99"
22 | },
23 | "allow-plugins": {
24 | "dealerdirect/phpcodesniffer-composer-installer": true
25 | }
26 | },
27 | "extra": {
28 | },
29 | "require": {
30 | "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
31 | "ext-simplexml": "*",
32 | "brick/math": "^0.13.1",
33 | "laminas/laminas-code": "^4.4",
34 | "laminas/laminas-http": "^2.15",
35 | "laminas/laminas-server": "^2.11",
36 | "laminas/laminas-stdlib": "^3.10.1",
37 | "laminas/laminas-xml": "^1.4.0"
38 | },
39 | "require-dev": {
40 | "laminas/laminas-coding-standard": "~3.1.0",
41 | "phpunit/phpunit": "^10.5.36",
42 | "psalm/plugin-phpunit": "^0.19.0",
43 | "vimeo/psalm": "^5.26.1"
44 | },
45 | "suggest": {
46 | "laminas/laminas-cache": "To support Laminas\\XmlRpc\\Server\\Cache usage"
47 | },
48 | "autoload": {
49 | "psr-4": {
50 | "Laminas\\XmlRpc\\": "src/"
51 | }
52 | },
53 | "autoload-dev": {
54 | "psr-4": {
55 | "LaminasTest\\XmlRpc\\": "test/"
56 | },
57 | "files": [
58 | "test/TestAsset/functions.php"
59 | ]
60 | },
61 | "scripts": {
62 | "check": [
63 | "@cs-check",
64 | "@test"
65 | ],
66 | "cs-check": "phpcs",
67 | "cs-fix": "phpcbf",
68 | "test": "phpunit --colors=always",
69 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
70 | "static-analysis": "psalm --shepherd --stats"
71 | },
72 | "conflict": {
73 | "zendframework/zend-xmlrpc": "*"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # laminas-xmlrpc
2 |
3 | [](https://github.com/laminas/laminas-xmlrpc/actions/workflows/continuous-integration.yml)
4 | [](https://shepherd.dev/github/laminas/laminas-xmlrpc)
5 | [](https://shepherd.dev/github/laminas/laminas-xmlrpc)
6 |
7 | > ## 🇷🇺 Русским гражданам
8 | >
9 | > Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в России, так и в Украине. Некоторые из нас родились в России. Некоторые из нас живут в России. У некоторых бабушки и дедушки сражались с фашистами во Второй мировой войне. Здесь никто не поддерживает фашизм.
10 | >
11 | > У одного из нас есть украинская родственница, которая спаслась из дома вместе с сыном. Поезд задержался из-за бомбежки на дороге впереди. У нас есть друзья, которые прячутся в бомбоубежищах. Мы с тревогой ждем весточки от них после воздушных налетов, которые беспорядочно наносят удары и попадают по больницам, школам, детским садам и домам. Мы не берем это из каких-либо СМИ. Мы наблюдаем это напрямую.
12 | >
13 | > Вы доверяете нам достаточно, чтоб использовать наши программы, и мы просим вас довериться нам вновь. Мы нуждаемся в помощи. Выходите и протестуйте против этой бесполезной войны. Остановите кровопролитие. Скажите "Нет войне!"
14 | >
15 | > ## 🇺🇸 To Citizens of Russia
16 | >
17 | > We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism.
18 | >
19 | > One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences.
20 | >
21 | > You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!"
22 |
23 | From its home page, XML-RPC is described as a ”...remote procedure calling using
24 | HTTP as the transport and XML as the encoding. XML-RPC is designed to be as
25 | simple as possible, while allowing complex data structures to be transmitted,
26 | processed and returned.”
27 |
28 | `Laminas\XmlRpc` provides support for both consuming remote XML-RPC services and
29 | building new XML-RPC servers.
30 |
31 | - File issues at https://github.com/laminas/laminas-xmlrpc/issues
32 | - Documentation is at https://docs.laminas.dev/laminas-xmlrpc/
33 |
--------------------------------------------------------------------------------
/src/Request/Http.php:
--------------------------------------------------------------------------------
1 | fault = new Fault(630);
53 | return;
54 | }
55 |
56 | $this->xml = $xml;
57 |
58 | $this->loadXml($xml);
59 | }
60 |
61 | /**
62 | * Retrieve the raw XML request
63 | *
64 | * @return string
65 | */
66 | public function getRawRequest()
67 | {
68 | return $this->xml;
69 | }
70 |
71 | /**
72 | * Get headers
73 | *
74 | * Gets all headers as key => value pairs and returns them.
75 | *
76 | * @return array
77 | */
78 | public function getHeaders()
79 | {
80 | if (null === $this->headers) {
81 | $this->headers = [];
82 | foreach ($_SERVER as $key => $value) {
83 | if (str_starts_with($key, 'HTTP_')) {
84 | $header = str_replace(
85 | ' ',
86 | '-',
87 | ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))
88 | );
89 | $this->headers[$header] = $value;
90 | }
91 | }
92 | }
93 |
94 | return $this->headers;
95 | }
96 |
97 | /**
98 | * Retrieve the full HTTP request, including headers and XML
99 | *
100 | * @return string
101 | */
102 | public function getFullRequest()
103 | {
104 | $request = '';
105 | foreach ($this->getHeaders() as $key => $value) {
106 | $request .= $key . ': ' . $value . "\n";
107 | }
108 |
109 | $request .= $this->xml;
110 |
111 | return $request;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Generator/AbstractGenerator.php:
--------------------------------------------------------------------------------
1 | setEncoding($encoding);
27 | $this->init();
28 | }
29 |
30 | /**
31 | * Initialize internal objects
32 | *
33 | * @return void
34 | */
35 | abstract protected function init();
36 |
37 | /**
38 | * Start XML element
39 | *
40 | * Method opens a new XML element with an element name and an optional value
41 | *
42 | * @param string $name XML tag name
43 | * @param string $value Optional value of the XML tag
44 | * @return AbstractGenerator Fluent interface
45 | */
46 | public function openElement($name, $value = null)
47 | {
48 | $this->openXmlElement($name);
49 | if ($value !== null) {
50 | $this->writeTextData($value);
51 | }
52 |
53 | return $this;
54 | }
55 |
56 | /**
57 | * End of an XML element
58 | *
59 | * Method marks the end of an XML element
60 | *
61 | * @param string $name XML tag name
62 | * @return AbstractGenerator Fluent interface
63 | */
64 | public function closeElement($name)
65 | {
66 | $this->closeXmlElement($name);
67 |
68 | return $this;
69 | }
70 |
71 | /**
72 | * Return encoding
73 | *
74 | * @return string
75 | */
76 | public function getEncoding()
77 | {
78 | return $this->encoding;
79 | }
80 |
81 | /**
82 | * Set XML encoding
83 | *
84 | * @param string $encoding
85 | * @return AbstractGenerator
86 | */
87 | public function setEncoding($encoding)
88 | {
89 | $this->encoding = $encoding;
90 | return $this;
91 | }
92 |
93 | /**
94 | * Returns the XML as a string and flushes all internal buffers
95 | *
96 | * @return string
97 | */
98 | public function flush()
99 | {
100 | $xml = $this->saveXml();
101 | $this->init();
102 | return $xml;
103 | }
104 |
105 | /**
106 | * Returns XML without document declaration
107 | */
108 | public function __toString(): string
109 | {
110 | return $this->stripDeclaration($this->saveXml());
111 | }
112 |
113 | /**
114 | * Removes XML declaration from a string
115 | *
116 | * @param string $xml
117 | * @return string
118 | */
119 | public function stripDeclaration($xml)
120 | {
121 | return preg_replace('/<\?xml version="1.0"( encoding="[^\"]*")?\?>\n/u', '', $xml);
122 | }
123 |
124 | /**
125 | * Start XML element
126 | *
127 | * @param string $name XML element name
128 | */
129 | abstract protected function openXmlElement($name);
130 |
131 | /**
132 | * Write XML text data into the currently opened XML element
133 | *
134 | * @param string $text
135 | */
136 | abstract protected function writeTextData($text);
137 |
138 | /**
139 | * End XML element
140 | *
141 | * @param string $name
142 | */
143 | abstract protected function closeXmlElement($name);
144 | }
145 |
--------------------------------------------------------------------------------
/src/Client/ServerIntrospection.php:
--------------------------------------------------------------------------------
1 | system = $client->getProxy('system');
21 | }
22 |
23 | /**
24 | * Returns the signature for each method on the server,
25 | * autodetecting whether system.multicall() is supported and
26 | * using it if so.
27 | *
28 | * @return array
29 | */
30 | public function getSignatureForEachMethod()
31 | {
32 | $methods = $this->listMethods();
33 |
34 | try {
35 | $signatures = $this->getSignatureForEachMethodByMulticall($methods);
36 | } catch (Exception\FaultException) {
37 | // degrade to looping
38 | }
39 |
40 | if (empty($signatures)) {
41 | $signatures = $this->getSignatureForEachMethodByLooping($methods);
42 | }
43 |
44 | return $signatures;
45 | }
46 |
47 | /**
48 | * Attempt to get the method signatures in one request via system.multicall().
49 | * This is a boxcar feature of XML-RPC and is found on fewer servers. However,
50 | * can significantly improve performance if present.
51 | *
52 | * @param array $methods
53 | * @throws Exception\IntrospectException
54 | * @return array array(array(return, param, param, param...))
55 | */
56 | public function getSignatureForEachMethodByMulticall($methods = null)
57 | {
58 | if ($methods === null) {
59 | $methods = $this->listMethods();
60 | }
61 |
62 | $multicallParams = [];
63 | foreach ($methods as $method) {
64 | $multicallParams[] = [
65 | 'methodName' => 'system.methodSignature',
66 | 'params' => [$method],
67 | ];
68 | }
69 |
70 | $serverSignatures = $this->system->multicall($multicallParams);
71 |
72 | if (! is_array($serverSignatures)) {
73 | $type = gettype($serverSignatures);
74 | $error = "Multicall return is malformed. Expected array, got $type";
75 | throw new Exception\IntrospectException($error);
76 | }
77 |
78 | if (count($serverSignatures) !== count($methods)) {
79 | $error = 'Bad number of signatures received from multicall';
80 | throw new Exception\IntrospectException($error);
81 | }
82 |
83 | // Create a new signatures array with the methods name as keys and the signature as value
84 | $signatures = [];
85 | foreach ($serverSignatures as $i => $signature) {
86 | $signatures[$methods[$i]] = $signature;
87 | }
88 |
89 | return $signatures;
90 | }
91 |
92 | /**
93 | * Get the method signatures for every method by
94 | * successively calling system.methodSignature
95 | *
96 | * @param array $methods
97 | * @return array
98 | */
99 | public function getSignatureForEachMethodByLooping($methods = null)
100 | {
101 | if ($methods === null) {
102 | $methods = $this->listMethods();
103 | }
104 |
105 | $signatures = [];
106 | foreach ($methods as $method) {
107 | $signatures[$method] = $this->getMethodSignature($method);
108 | }
109 |
110 | return $signatures;
111 | }
112 |
113 | /**
114 | * Call system.methodSignature() for the given method
115 | *
116 | * @param array $method
117 | * @throws Exception\IntrospectException
118 | * @return array array(array(return, param, param, param...))
119 | */
120 | public function getMethodSignature($method)
121 | {
122 | $signature = $this->system->methodSignature($method);
123 | if (! is_array($signature)) {
124 | $error = 'Invalid signature for method "' . $method . '"';
125 | throw new Exception\IntrospectException($error);
126 | }
127 | return $signature;
128 | }
129 |
130 | /**
131 | * Call system.listMethods()
132 | *
133 | * @return array array(method, method, method...)
134 | */
135 | public function listMethods()
136 | {
137 | return $this->system->listMethods();
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Server/System.php:
--------------------------------------------------------------------------------
1 | server = $server;
29 | }
30 |
31 | /**
32 | * List all available XMLRPC methods
33 | *
34 | * Returns an array of methods.
35 | *
36 | * @return array
37 | */
38 | public function listMethods()
39 | {
40 | $table = $this->server->getDispatchTable()->getMethods();
41 | return array_keys($table);
42 | }
43 |
44 | /**
45 | * Display help message for an XMLRPC method
46 | *
47 | * @param string $method
48 | * @throws InvalidArgumentException
49 | * @return string
50 | */
51 | public function methodHelp($method)
52 | {
53 | $table = $this->server->getDispatchTable();
54 | if (! $table->hasMethod($method)) {
55 | throw new InvalidArgumentException('Method "' . $method . '" does not exist', 640);
56 | }
57 |
58 | return $table->getMethod($method)->getMethodHelp();
59 | }
60 |
61 | /**
62 | * Return a method signature
63 | *
64 | * @param string $method
65 | * @throws InvalidArgumentException
66 | * @return array
67 | */
68 | public function methodSignature($method)
69 | {
70 | $table = $this->server->getDispatchTable();
71 | if (! $table->hasMethod($method)) {
72 | throw new InvalidArgumentException('Method "' . $method . '" does not exist', 640);
73 | }
74 | $method = $table->getMethod($method)->toArray();
75 | return $method['prototypes'];
76 | }
77 |
78 | /**
79 | * Multicall - boxcar feature of XML-RPC for calling multiple methods
80 | * in a single request.
81 | *
82 | * Expects an array of structs representing method calls, each element
83 | * having the keys:
84 | * - methodName
85 | * - params
86 | *
87 | * Returns an array of responses, one for each method called, with the value
88 | * returned by the method. If an error occurs for a given method, returns a
89 | * struct with a fault response.
90 | *
91 | * @see http://www.xmlrpc.com/discuss/msgReader$1208
92 | *
93 | * @param array $methods
94 | * @return array
95 | */
96 | public function multicall($methods)
97 | {
98 | $responses = [];
99 | foreach ($methods as $method) {
100 | $fault = false;
101 | if (! is_array($method)) {
102 | $fault = $this->server->fault('system.multicall expects each method to be a struct', 601);
103 | } elseif (! isset($method['methodName'])) {
104 | $fault = $this->server->fault('Missing methodName: ' . var_export($methods, 1), 602);
105 | } elseif (! isset($method['params'])) {
106 | $fault = $this->server->fault('Missing params', 603);
107 | } elseif (! is_array($method['params'])) {
108 | $fault = $this->server->fault('Params must be an array', 604);
109 | } else {
110 | if ('system.multicall' === $method['methodName']) {
111 | // don't allow recursive calls to multicall
112 | $fault = $this->server->fault('Recursive system.multicall forbidden', 605);
113 | }
114 | }
115 |
116 | if (! $fault) {
117 | try {
118 | $request = new Request();
119 | $request->setMethod($method['methodName']);
120 | $request->setParams($method['params']);
121 | $response = $this->server->handle($request);
122 | if (
123 | $response instanceof Fault
124 | || $response->isFault()
125 | ) {
126 | $fault = $response;
127 | } else {
128 | $responses[] = $response->getReturnValue();
129 | }
130 | } catch (Exception $e) {
131 | $fault = $this->server->fault($e);
132 | }
133 | }
134 |
135 | if ($fault) {
136 | $responses[] = [
137 | 'faultCode' => $fault->getCode(),
138 | 'faultString' => $fault->getMessage(),
139 | ];
140 | }
141 | }
142 |
143 | return $responses;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/Server/Fault.php:
--------------------------------------------------------------------------------
1 | true];
36 |
37 | /** @var array Array of fault observers */
38 | protected static $observers = [];
39 |
40 | public function __construct(Exception $e)
41 | {
42 | $this->exception = $e;
43 | $code = 404;
44 | $message = 'Unknown error';
45 |
46 | foreach (array_keys(static::$faultExceptionClasses) as $class) {
47 | if ($e instanceof $class) {
48 | $code = $e->getCode();
49 | $message = $e->getMessage();
50 | break;
51 | }
52 | }
53 |
54 | parent::__construct($code, $message);
55 |
56 | // Notify exception observers, if present
57 | if (! empty(static::$observers)) {
58 | foreach (array_keys(static::$observers) as $observer) {
59 | $observer::observe($this);
60 | }
61 | }
62 | }
63 |
64 | /**
65 | * Return Laminas\XmlRpc\Server\Fault instance
66 | *
67 | * @return Fault
68 | */
69 | public static function getInstance(Exception $e)
70 | {
71 | return new static($e);
72 | }
73 |
74 | /**
75 | * Attach valid exceptions that can be used to define xmlrpc faults
76 | *
77 | * @param string|array $classes Class name or array of class names
78 | * @return void
79 | */
80 | public static function attachFaultException($classes)
81 | {
82 | if (! is_array($classes)) {
83 | $classes = (array) $classes;
84 | }
85 |
86 | foreach ($classes as $class) {
87 | if (is_string($class) && class_exists($class)) {
88 | static::$faultExceptionClasses[$class] = true;
89 | }
90 | }
91 | }
92 |
93 | /**
94 | * Detach fault exception classes
95 | *
96 | * @param string|array $classes Class name or array of class names
97 | * @return void
98 | */
99 | public static function detachFaultException($classes)
100 | {
101 | if (! is_array($classes)) {
102 | $classes = (array) $classes;
103 | }
104 |
105 | foreach ($classes as $class) {
106 | if (is_string($class) && isset(static::$faultExceptionClasses[$class])) {
107 | unset(static::$faultExceptionClasses[$class]);
108 | }
109 | }
110 | }
111 |
112 | /**
113 | * Attach an observer class
114 | *
115 | * Allows observation of xmlrpc server faults, thus allowing logging or mail
116 | * notification of fault responses on the xmlrpc server.
117 | *
118 | * Expects a valid class name; that class must have a public static method
119 | * 'observe' that accepts an exception as its sole argument.
120 | *
121 | * @param string $class
122 | * @return bool
123 | */
124 | public static function attachObserver($class)
125 | {
126 | if (! is_string($class) || ! class_exists($class) || ! is_callable([$class, 'observe'])) {
127 | return false;
128 | }
129 |
130 | if (! isset(static::$observers[$class])) {
131 | static::$observers[$class] = true;
132 | }
133 |
134 | return true;
135 | }
136 |
137 | /**
138 | * Detach an observer
139 | *
140 | * @param string $class
141 | * @return bool
142 | */
143 | public static function detachObserver($class)
144 | {
145 | if (! isset(static::$observers[$class])) {
146 | return false;
147 | }
148 |
149 | unset(static::$observers[$class]);
150 | return true;
151 | }
152 |
153 | /**
154 | * Retrieve the exception
155 | *
156 | * @access public
157 | * @return Exception
158 | */
159 | public function getException()
160 | {
161 | return $this->exception;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/Response.php:
--------------------------------------------------------------------------------
1 | setReturnValue($return, $type);
59 | }
60 |
61 | /**
62 | * Set encoding to use in response
63 | *
64 | * @param string $encoding
65 | * @return Response
66 | */
67 | public function setEncoding($encoding)
68 | {
69 | $this->encoding = $encoding;
70 | AbstractValue::setEncoding($encoding);
71 | return $this;
72 | }
73 |
74 | /**
75 | * Retrieve current response encoding
76 | *
77 | * @return string
78 | */
79 | public function getEncoding()
80 | {
81 | return $this->encoding;
82 | }
83 |
84 | /**
85 | * Set the return value
86 | *
87 | * Sets the return value, with optional type hinting if provided.
88 | *
89 | * @param mixed $value
90 | * @param string $type
91 | * @return void
92 | */
93 | public function setReturnValue($value, $type = null)
94 | {
95 | $this->return = $value;
96 | $this->type = (string) $type;
97 | }
98 |
99 | /**
100 | * Retrieve the return value
101 | *
102 | * @return mixed
103 | */
104 | public function getReturnValue()
105 | {
106 | return $this->return;
107 | }
108 |
109 | /**
110 | * Retrieve the XMLRPC value for the return value
111 | *
112 | * @return AbstractValue
113 | */
114 | protected function getXmlRpcReturn()
115 | {
116 | return AbstractValue::getXmlRpcValue($this->return);
117 | }
118 |
119 | /**
120 | * Is the response a fault response?
121 | *
122 | * @return bool
123 | */
124 | public function isFault()
125 | {
126 | return $this->fault instanceof Fault;
127 | }
128 |
129 | /**
130 | * Returns the fault, if any.
131 | *
132 | * @return null|Fault
133 | */
134 | public function getFault()
135 | {
136 | return $this->fault;
137 | }
138 |
139 | /**
140 | * Load a response from an XML response
141 | *
142 | * Attempts to load a response from an XMLRPC response, autodetecting if it
143 | * is a fault response.
144 | *
145 | * You may optionally pass a bitmask of LIBXML options via the
146 | * $libXmlOptions parameter; as an example, you might use LIBXML_PARSEHUGE.
147 | * See https://www.php.net/manual/en/libxml.constants.php for a full list.
148 | *
149 | * @param string $response
150 | * @param int $libXmlOptions Bitmask of LIBXML options to use for XML * operations
151 | * @throws Exception\ValueException If invalid XML.
152 | * @return bool True if a valid XMLRPC response, false if a fault
153 | * response or invalid input
154 | */
155 | public function loadXml($response, int $libXmlOptions = 0)
156 | {
157 | if (! is_string($response)) {
158 | $this->fault = new Fault(650);
159 | $this->fault->setEncoding($this->getEncoding());
160 | return false;
161 | }
162 |
163 | try {
164 | $xml = XmlSecurity::scan($response, null, $libXmlOptions);
165 | } catch (RuntimeException $e) {
166 | $this->fault = new Fault(651);
167 | $this->fault->setEncoding($this->getEncoding());
168 | return false;
169 | }
170 |
171 | if (isset($xml->fault) && is_object($xml->fault)) {
172 | // fault response
173 | $this->fault = new Fault();
174 | $this->fault->setEncoding($this->getEncoding());
175 | $this->fault->loadXml($response);
176 | return false;
177 | }
178 |
179 | if (! isset($xml->params)) {
180 | // Invalid response
181 | $this->fault = new Fault(652);
182 | $this->fault->setEncoding($this->getEncoding());
183 | return false;
184 | }
185 |
186 | try {
187 | if (! isset($xml->params->param, $xml->params->param->value)) {
188 | throw new Exception\ValueException('Missing XML-RPC value in XML');
189 | }
190 | $valueXml = $xml->params->param->value->asXML();
191 | $value = AbstractValue::getXmlRpcValue($valueXml, AbstractValue::XML_STRING, $libXmlOptions);
192 | } catch (Exception\ValueException) {
193 | $this->fault = new Fault(653);
194 | $this->fault->setEncoding($this->getEncoding());
195 | return false;
196 | }
197 |
198 | $this->setReturnValue($value->getValue());
199 | return true;
200 | }
201 |
202 | /**
203 | * Return response as XML
204 | *
205 | * @return string
206 | */
207 | public function saveXml()
208 | {
209 | $value = $this->getXmlRpcReturn();
210 | $generator = AbstractValue::getGenerator();
211 | $generator->openElement('methodResponse')
212 | ->openElement('params')
213 | ->openElement('param');
214 | $value->generateXml();
215 | $generator->closeElement('param')
216 | ->closeElement('params')
217 | ->closeElement('methodResponse');
218 |
219 | return $generator->flush();
220 | }
221 |
222 | /**
223 | * Return XML response
224 | */
225 | public function __toString(): string
226 | {
227 | return $this->saveXML();
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/Fault.php:
--------------------------------------------------------------------------------
1 | messages
50 | *
51 | * @var array
52 | */
53 | protected $internal = [
54 | 404 => 'Unknown Error',
55 |
56 | // 610 - 619 reflection errors
57 | 610 => 'Invalid method class',
58 | 611 => 'Unable to attach function or callback; not callable',
59 | 612 => 'Unable to load array; not an array',
60 | 613 => 'One or more method records are corrupt or otherwise unusable',
61 |
62 | // 620 - 629 dispatch errors
63 | 620 => 'Method does not exist',
64 | 621 => 'Error instantiating class to invoke method',
65 | 622 => 'Method missing implementation',
66 | 623 => 'Calling parameters do not match signature',
67 |
68 | // 630 - 639 request errors
69 | 630 => 'Unable to read request',
70 | 631 => 'Failed to parse request',
71 | 632 => 'Invalid request, no method passed; request must contain a \'methodName\' tag',
72 | 633 => 'Param must contain a value',
73 | 634 => 'Invalid method name',
74 | 635 => 'Invalid XML provided to request',
75 | 636 => 'Error creating xmlrpc value',
76 |
77 | // 640 - 649 system.* errors
78 | 640 => 'Method does not exist',
79 |
80 | // 650 - 659 response errors
81 | 650 => 'Invalid XML provided for response',
82 | 651 => 'Failed to parse response',
83 | 652 => 'Invalid response',
84 | 653 => 'Invalid XMLRPC value in response',
85 | ];
86 |
87 | /**
88 | * Constructor
89 | *
90 | * @param int $code
91 | * @param string $message
92 | */
93 | public function __construct($code = 404, $message = '')
94 | {
95 | $this->setCode($code);
96 | $code = $this->getCode();
97 |
98 | if (empty($message) && isset($this->internal[$code])) {
99 | $message = $this->internal[$code];
100 | } elseif (empty($message)) {
101 | $message = $this->internal[404];
102 | }
103 | $this->setMessage($message);
104 | }
105 |
106 | /**
107 | * Set the fault code
108 | *
109 | * @param int $code
110 | * @return Fault
111 | */
112 | public function setCode($code)
113 | {
114 | $this->code = (int) $code;
115 | return $this;
116 | }
117 |
118 | /**
119 | * Return fault code
120 | *
121 | * @return int
122 | */
123 | public function getCode()
124 | {
125 | return $this->code;
126 | }
127 |
128 | /**
129 | * Retrieve fault message
130 | *
131 | * @param string $message
132 | * @return Fault
133 | */
134 | public function setMessage($message)
135 | {
136 | $this->message = (string) $message;
137 | return $this;
138 | }
139 |
140 | /**
141 | * Retrieve fault message
142 | *
143 | * @return string
144 | */
145 | public function getMessage()
146 | {
147 | return $this->message;
148 | }
149 |
150 | /**
151 | * Set encoding to use in fault response
152 | *
153 | * @param string $encoding
154 | * @return Fault
155 | */
156 | public function setEncoding($encoding)
157 | {
158 | $this->encoding = $encoding;
159 | AbstractValue::setEncoding($encoding);
160 | return $this;
161 | }
162 |
163 | /**
164 | * Retrieve current fault encoding
165 | *
166 | * @return string
167 | */
168 | public function getEncoding()
169 | {
170 | return $this->encoding;
171 | }
172 |
173 | /**
174 | * Load an XMLRPC fault from XML
175 | *
176 | * @param string $fault
177 | * @return bool Returns true if successfully loaded fault response, false
178 | * if response was not a fault response
179 | * @throws Exception\ExceptionInterface If no or faulty XML provided, or if fault
180 | * response does not contain either code or message.
181 | */
182 | public function loadXml($fault)
183 | {
184 | if (! is_string($fault)) {
185 | throw new Exception\InvalidArgumentException('Invalid XML provided to fault');
186 | }
187 |
188 | $xmlErrorsFlag = libxml_use_internal_errors(true);
189 | try {
190 | $xml = XmlSecurity::scan($fault);
191 | } catch (RuntimeException $e) {
192 | // Unsecure XML
193 | throw new Exception\RuntimeException('Failed to parse XML fault: ' . $e->getMessage(), 500, $e);
194 | }
195 | if (! $xml instanceof SimpleXMLElement) {
196 | $errors = libxml_get_errors();
197 | $errors = array_reduce($errors, function ($result, $item) {
198 | if (empty($result)) {
199 | return $item->message;
200 | }
201 | return $result . '; ' . $item->message;
202 | }, '');
203 | libxml_use_internal_errors($xmlErrorsFlag);
204 | throw new Exception\InvalidArgumentException('Failed to parse XML fault: ' . $errors, 500);
205 | }
206 | libxml_use_internal_errors($xmlErrorsFlag);
207 |
208 | // Check for fault
209 | if (! isset($xml->fault)) {
210 | // Not a fault
211 | return false;
212 | }
213 |
214 | if (! isset($xml->fault->value->struct)) {
215 | // not a proper fault
216 | throw new Exception\InvalidArgumentException('Invalid fault structure', 500);
217 | }
218 |
219 | $structXml = $xml->fault->value->asXML();
220 | $struct = AbstractValue::getXmlRpcValue($structXml, AbstractValue::XML_STRING);
221 | $struct = $struct->getValue();
222 |
223 | if (isset($struct['faultCode'])) {
224 | $code = $struct['faultCode'];
225 | }
226 | if (isset($struct['faultString'])) {
227 | $message = $struct['faultString'];
228 | }
229 |
230 | if (empty($code) && empty($message)) {
231 | throw new Exception\InvalidArgumentException('Fault code and string required');
232 | }
233 |
234 | if (empty($code)) {
235 | $code = '404';
236 | }
237 |
238 | if (empty($message)) {
239 | if (isset($this->internal[$code])) {
240 | $message = $this->internal[$code];
241 | } else {
242 | $message = $this->internal[404];
243 | }
244 | }
245 |
246 | $this->setCode($code);
247 | $this->setMessage($message);
248 |
249 | return true;
250 | }
251 |
252 | /**
253 | * Determine if an XML response is an XMLRPC fault
254 | *
255 | * @param string $xml
256 | * @return bool
257 | */
258 | public static function isFault($xml)
259 | {
260 | $fault = new static();
261 | try {
262 | $isFault = $fault->loadXml($xml);
263 | } catch (Exception\ExceptionInterface) {
264 | $isFault = false;
265 | }
266 |
267 | return $isFault;
268 | }
269 |
270 | /**
271 | * Serialize fault to XML
272 | *
273 | * @return string
274 | */
275 | public function saveXml()
276 | {
277 | // Create fault value
278 | $faultStruct = [
279 | 'faultCode' => $this->getCode(),
280 | 'faultString' => $this->getMessage(),
281 | ];
282 | $value = AbstractValue::getXmlRpcValue($faultStruct);
283 |
284 | $generator = AbstractValue::getGenerator();
285 | $generator->openElement('methodResponse')
286 | ->openElement('fault');
287 | $value->generateXml();
288 | $generator->closeElement('fault')
289 | ->closeElement('methodResponse');
290 |
291 | return $generator->flush();
292 | }
293 |
294 | /**
295 | * Return XML fault response
296 | */
297 | public function __toString(): string
298 | {
299 | return $this->saveXML();
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/src/Client.php:
--------------------------------------------------------------------------------
1 | httpClient = new Http\Client();
94 | } else {
95 | $this->httpClient = $httpClient;
96 | }
97 |
98 | $this->introspector = new ServerIntrospection($this);
99 | $this->serverAddress = $server;
100 | }
101 |
102 | /**
103 | * Sets the HTTP client object to use for connecting the XML-RPC server.
104 | *
105 | * @return \Laminas\Http\Client
106 | */
107 | public function setHttpClient(Http\Client $httpClient)
108 | {
109 | return $this->httpClient = $httpClient;
110 | }
111 |
112 | /**
113 | * Gets the HTTP client object.
114 | *
115 | * @return \Laminas\Http\Client
116 | */
117 | public function getHttpClient()
118 | {
119 | return $this->httpClient;
120 | }
121 |
122 | /**
123 | * Sets the object used to introspect remote servers
124 | *
125 | * @return ServerIntrospection
126 | */
127 | public function setIntrospector(ServerIntrospection $introspector)
128 | {
129 | return $this->introspector = $introspector;
130 | }
131 |
132 | /**
133 | * Gets the introspection object.
134 | *
135 | * @return ServerIntrospection
136 | */
137 | public function getIntrospector()
138 | {
139 | return $this->introspector;
140 | }
141 |
142 | /**
143 | * The request of the last method call
144 | *
145 | * @return Request
146 | */
147 | public function getLastRequest()
148 | {
149 | return $this->lastRequest;
150 | }
151 |
152 | /**
153 | * The response received from the last method call
154 | *
155 | * @return Response
156 | */
157 | public function getLastResponse()
158 | {
159 | return $this->lastResponse;
160 | }
161 |
162 | /**
163 | * Returns a proxy object for more convenient method calls
164 | *
165 | * @param string $namespace Namespace to proxy or empty string for none
166 | * @return ServerProxy
167 | */
168 | public function getProxy($namespace = '')
169 | {
170 | if (empty($this->proxyCache[$namespace])) {
171 | $proxy = new ServerProxy($this, $namespace);
172 | $this->proxyCache[$namespace] = $proxy;
173 | }
174 | return $this->proxyCache[$namespace];
175 | }
176 |
177 | /**
178 | * Set skip system lookup flag
179 | *
180 | * @param bool $flag
181 | * @return Client
182 | */
183 | public function setSkipSystemLookup($flag = true)
184 | {
185 | $this->skipSystemLookup = (bool) $flag;
186 | return $this;
187 | }
188 |
189 | /**
190 | * Skip system lookup when determining if parameter should be array or struct?
191 | *
192 | * @return bool
193 | */
194 | public function skipSystemLookup()
195 | {
196 | return $this->skipSystemLookup;
197 | }
198 |
199 | /**
200 | * Perform an XML-RPC request and return a response.
201 | *
202 | * You may optionally pass a bitmask of LIBXML options via the
203 | * $libXmlOptions parameter; as an example, you might use LIBXML_PARSEHUGE.
204 | * See https://www.php.net/manual/en/libxml.constants.php for a full list.
205 | *
206 | * @param Request $request
207 | * @param null|Response $response
208 | * @param int $libXmlOptions Bitmask of LIBXML options to use for XML * operations
209 | * @throws InvalidArgumentException
210 | * @throws RuntimeException
211 | * @throws HttpException
212 | * @throws ValueException
213 | * @return void
214 | */
215 | public function doRequest($request, $response = null, int $libXmlOptions = 0)
216 | {
217 | $this->lastRequest = $request;
218 |
219 | if (PHP_VERSION_ID < 50600) {
220 | iconv_set_encoding('input_encoding', 'UTF-8');
221 | iconv_set_encoding('output_encoding', 'UTF-8');
222 | iconv_set_encoding('internal_encoding', 'UTF-8');
223 | } else {
224 | ini_set('default_charset', 'UTF-8');
225 | }
226 |
227 | $http = $this->getHttpClient();
228 | $httpRequest = $http->getRequest();
229 | if ($httpRequest->getUriString() === null) {
230 | $http->setUri($this->serverAddress);
231 | }
232 |
233 | $headers = $httpRequest->getHeaders();
234 |
235 | if (! $headers->has('Content-Type')) {
236 | $headers->addHeaderLine('Content-Type', 'text/xml; charset=utf-8');
237 | }
238 |
239 | if (! $headers->has('Accept')) {
240 | $headers->addHeaderLine('Accept', 'text/xml');
241 | }
242 |
243 | if (! $headers->has('user-agent')) {
244 | $headers->addHeaderLine('user-agent', 'Laminas_XmlRpc_Client');
245 | }
246 |
247 | $xml = $this->lastRequest->__toString();
248 | $http->setRawBody($xml);
249 | $httpResponse = $http->setMethod('POST')->send();
250 |
251 | if (! $httpResponse->isSuccess()) {
252 | /**
253 | * Exception thrown when an HTTP error occurs
254 | */
255 | throw new HttpException(
256 | $httpResponse->getReasonPhrase(),
257 | $httpResponse->getStatusCode()
258 | );
259 | }
260 |
261 | if ($response === null) {
262 | $response = new Response();
263 | }
264 |
265 | $this->lastResponse = $response;
266 | $this->lastResponse->loadXml(trim($httpResponse->getBody()), $libXmlOptions);
267 | }
268 |
269 | /**
270 | * Send an XML-RPC request to the service (for a specific method)
271 | *
272 | * @param string $method Name of the method we want to call
273 | * @param array $params Array of parameters for the method
274 | * @return mixed
275 | * @throws FaultException
276 | */
277 | public function call($method, $params = [])
278 | {
279 | if (! $this->skipSystemLookup() && (! str_starts_with($method, 'system.'))) {
280 | // Ensure empty array/struct params are cast correctly
281 | // If system.* methods are not available, bypass. (Laminas-2978)
282 | $success = true;
283 | try {
284 | $signatures = $this->getIntrospector()->getMethodSignature($method);
285 | } catch (ExceptionInterface) {
286 | $success = false;
287 | }
288 | if ($success) {
289 | $validTypes = [
290 | AbstractValue::XMLRPC_TYPE_ARRAY,
291 | AbstractValue::XMLRPC_TYPE_BASE64,
292 | AbstractValue::XMLRPC_TYPE_BOOLEAN,
293 | AbstractValue::XMLRPC_TYPE_DATETIME,
294 | AbstractValue::XMLRPC_TYPE_DOUBLE,
295 | AbstractValue::XMLRPC_TYPE_I4,
296 | AbstractValue::XMLRPC_TYPE_INTEGER,
297 | AbstractValue::XMLRPC_TYPE_NIL,
298 | AbstractValue::XMLRPC_TYPE_STRING,
299 | AbstractValue::XMLRPC_TYPE_STRUCT,
300 | ];
301 |
302 | if (! is_array($params)) {
303 | $params = [$params];
304 | }
305 | foreach ($params as $key => $param) {
306 | if ($param instanceof AbstractValue) {
307 | continue;
308 | }
309 |
310 | assert(isset($signatures));
311 |
312 | if (count($signatures) > 1) {
313 | $type = AbstractValue::getXmlRpcTypeByValue($param);
314 | foreach ($signatures as $signature) {
315 | if (! is_array($signature)) {
316 | continue;
317 | }
318 | if (isset($signature['parameters'][$key])) {
319 | if ($signature['parameters'][$key] === $type) {
320 | break;
321 | }
322 | }
323 | }
324 | } elseif (isset($signatures[0]['parameters'][$key])) {
325 | $type = $signatures[0]['parameters'][$key];
326 | } else {
327 | $type = null;
328 | }
329 |
330 | if (! is_string($type) || ! in_array($type, $validTypes)) {
331 | $type = AbstractValue::AUTO_DETECT_TYPE;
332 | }
333 |
334 | /** @psalm-var AbstractValue::XMLRPC_TYPE_* $type */
335 |
336 | $params[$key] = AbstractValue::getXmlRpcValue($param, $type);
337 | }
338 | }
339 | }
340 |
341 | $request = $this->createRequest($method, $params);
342 |
343 | $this->doRequest($request);
344 |
345 | if ($this->lastResponse->isFault()) {
346 | $fault = $this->lastResponse->getFault();
347 | /**
348 | * Exception thrown when an XML-RPC fault is returned
349 | */
350 | throw new FaultException(
351 | $fault->getMessage(),
352 | $fault->getCode()
353 | );
354 | }
355 |
356 | return $this->lastResponse->getReturnValue();
357 | }
358 |
359 | /**
360 | * Create request object
361 | *
362 | * @param string $method
363 | * @param array $params
364 | * @return Request
365 | */
366 | protected function createRequest($method, $params)
367 | {
368 | return new Request($method, $params);
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/src/Request.php:
--------------------------------------------------------------------------------
1 | setMethod($method);
96 | }
97 |
98 | if ($params !== null) {
99 | $this->setParams($params);
100 | }
101 | }
102 |
103 | /**
104 | * Set encoding to use in request
105 | *
106 | * @param string $encoding
107 | * @return Request
108 | */
109 | public function setEncoding($encoding)
110 | {
111 | $this->encoding = $encoding;
112 | AbstractValue::setEncoding($encoding);
113 | return $this;
114 | }
115 |
116 | /**
117 | * Retrieve current request encoding
118 | *
119 | * @return string
120 | */
121 | public function getEncoding()
122 | {
123 | return $this->encoding;
124 | }
125 |
126 | /**
127 | * Set method to call
128 | *
129 | * @param string $method
130 | * @return bool Returns true on success, false if method name is invalid
131 | */
132 | public function setMethod($method)
133 | {
134 | if (! is_string($method) || ! preg_match('/^[a-z0-9_.:\\\\\/]+$/i', $method)) {
135 | $this->fault = new Fault(634, 'Invalid method name ("' . $method . '")');
136 | $this->fault->setEncoding($this->getEncoding());
137 | return false;
138 | }
139 |
140 | $this->method = $method;
141 | return true;
142 | }
143 |
144 | /**
145 | * Retrieve call method
146 | *
147 | * @return string
148 | */
149 | public function getMethod()
150 | {
151 | return $this->method;
152 | }
153 |
154 | /**
155 | * Add a parameter to the parameter stack
156 | *
157 | * Adds a parameter to the parameter stack, associating it with the type
158 | * $type if provided
159 | *
160 | * @param mixed $value
161 | * @param string $type Optional; type hinting
162 | * @return void
163 | */
164 | public function addParam($value, $type = null)
165 | {
166 | $this->params[] = $value;
167 | if (null === $type) {
168 | // Detect type if not provided explicitly
169 | if ($value instanceof AbstractValue) {
170 | $type = $value->getType();
171 | } else {
172 | $xmlRpcValue = AbstractValue::getXmlRpcValue($value);
173 | $type = $xmlRpcValue->getType();
174 | }
175 | }
176 | $this->types[] = $type;
177 | $this->xmlRpcParams[] = ['value' => $value, 'type' => $type];
178 | }
179 |
180 | /**
181 | * Set the parameters array
182 | *
183 | * If called with a single, array value, that array is used to set the
184 | * parameters stack. If called with multiple values or a single non-array
185 | * value, the arguments are used to set the parameters stack.
186 | *
187 | * Best is to call with array of the format, in order to allow type hinting
188 | * when creating the XMLRPC values for each parameter:
189 | *
190 | * $array = array(
191 | * array(
192 | * 'value' => $value,
193 | * 'type' => $type
194 | * )[, ... ]
195 | * );
196 | *
197 | *
198 | * @access public
199 | * @return void
200 | */
201 | public function setParams()
202 | {
203 | $argc = func_num_args();
204 | $argv = func_get_args();
205 | if (0 === $argc) {
206 | return;
207 | }
208 |
209 | if ((1 === $argc) && is_array($argv[0])) {
210 | $params = [];
211 | $types = [];
212 | $wellFormed = true;
213 | foreach ($argv[0] as $arg) {
214 | if (! is_array($arg) || ! isset($arg['value'])) {
215 | $wellFormed = false;
216 | break;
217 | }
218 | $params[] = $arg['value'];
219 |
220 | if (! isset($arg['type'])) {
221 | $xmlRpcValue = AbstractValue::getXmlRpcValue($arg['value']);
222 | $arg['type'] = $xmlRpcValue->getType();
223 | }
224 | $types[] = $arg['type'];
225 | }
226 | if ($wellFormed) {
227 | $this->xmlRpcParams = $argv[0];
228 | $this->params = $params;
229 | $this->types = $types;
230 | } else {
231 | $this->params = $argv[0];
232 | $this->types = [];
233 | $xmlRpcParams = [];
234 | foreach ($argv[0] as $arg) {
235 | if ($arg instanceof AbstractValue) {
236 | $type = $arg->getType();
237 | } else {
238 | $xmlRpcValue = AbstractValue::getXmlRpcValue($arg);
239 | $type = $xmlRpcValue->getType();
240 | }
241 | $xmlRpcParams[] = ['value' => $arg, 'type' => $type];
242 | $this->types[] = $type;
243 | }
244 | $this->xmlRpcParams = $xmlRpcParams;
245 | }
246 | return;
247 | }
248 |
249 | $this->params = $argv;
250 | $this->types = [];
251 | $xmlRpcParams = [];
252 | foreach ($argv as $arg) {
253 | if ($arg instanceof AbstractValue) {
254 | $type = $arg->getType();
255 | } else {
256 | $xmlRpcValue = AbstractValue::getXmlRpcValue($arg);
257 | $type = $xmlRpcValue->getType();
258 | }
259 | $xmlRpcParams[] = ['value' => $arg, 'type' => $type];
260 | $this->types[] = $type;
261 | }
262 | $this->xmlRpcParams = $xmlRpcParams;
263 | }
264 |
265 | /**
266 | * Retrieve the array of parameters
267 | *
268 | * @return array
269 | */
270 | public function getParams()
271 | {
272 | return $this->params;
273 | }
274 |
275 | /**
276 | * Return parameter types
277 | *
278 | * @return array
279 | */
280 | public function getTypes()
281 | {
282 | return $this->types;
283 | }
284 |
285 | /**
286 | * Load XML and parse into request components
287 | *
288 | * You may optionally pass a bitmask of LIBXML options via the
289 | * $libXmlOptions parameter; as an example, you might use LIBXML_PARSEHUGE.
290 | * See https://www.php.net/manual/en/libxml.constants.php for a full list.
291 | *
292 | * @param string $request
293 | * @param int $libXmlOptions Bitmask of LIBXML options to use for XML * operations
294 | * @throws ValueException If invalid XML.
295 | * @return bool True on success, false if an error occurred.
296 | */
297 | public function loadXml($request, int $libXmlOptions = 0)
298 | {
299 | if (! is_string($request)) {
300 | $this->fault = new Fault(635);
301 | $this->fault->setEncoding($this->getEncoding());
302 | return false;
303 | }
304 |
305 | $xmlErrorsFlag = libxml_use_internal_errors(true);
306 |
307 | try {
308 | $dom = new DOMDocument();
309 | $dom->loadXML($request, $libXmlOptions);
310 | foreach ($dom->childNodes as $child) {
311 | if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
312 | throw new ValueException(
313 | 'Invalid XML: Detected use of illegal DOCTYPE'
314 | );
315 | }
316 | }
317 | ErrorHandler::start();
318 | $xml = simplexml_import_dom($dom);
319 | $error = ErrorHandler::stop();
320 | libxml_use_internal_errors($xmlErrorsFlag);
321 | } catch (Exception) {
322 | // Not valid XML
323 | $this->fault = new Fault(631);
324 | $this->fault->setEncoding($this->getEncoding());
325 | libxml_use_internal_errors($xmlErrorsFlag);
326 | return false;
327 | }
328 | if (! $xml instanceof SimpleXMLElement || $error) {
329 | // Not valid XML
330 | $this->fault = new Fault(631);
331 | $this->fault->setEncoding($this->getEncoding());
332 | libxml_use_internal_errors($xmlErrorsFlag);
333 | return false;
334 | }
335 |
336 | // Check for method name
337 | if (! isset($xml->methodName) || (string) $xml->methodName === '') {
338 | // Missing method name
339 | $this->fault = new Fault(632);
340 | $this->fault->setEncoding($this->getEncoding());
341 | return false;
342 | }
343 |
344 | $this->method = (string) $xml->methodName;
345 |
346 | // Check for parameters
347 | if ($xml->params instanceof SimpleXMLElement && $xml->params->count() > 0) {
348 | $types = [];
349 | $argv = [];
350 | $children = $xml->params->children();
351 | assert($children !== null);
352 | foreach ($children as $param) {
353 | if (! isset($param->value)) {
354 | $this->fault = new Fault(633);
355 | $this->fault->setEncoding($this->getEncoding());
356 | return false;
357 | }
358 |
359 | try {
360 | $param = AbstractValue::getXmlRpcValue($param->value, AbstractValue::XML_STRING);
361 | $types[] = $param->getType();
362 | $argv[] = $param->getValue();
363 | } catch (Exception) {
364 | $this->fault = new Fault(636);
365 | $this->fault->setEncoding($this->getEncoding());
366 | return false;
367 | }
368 | }
369 |
370 | $this->types = $types;
371 | $this->params = $argv;
372 | }
373 |
374 | $this->xml = $request;
375 |
376 | return true;
377 | }
378 |
379 | /**
380 | * Does the current request contain errors and should it return a fault
381 | * response?
382 | *
383 | * @return bool
384 | */
385 | public function isFault()
386 | {
387 | return $this->fault instanceof Fault;
388 | }
389 |
390 | /**
391 | * Retrieve the fault response, if any
392 | *
393 | * @return null|Fault
394 | */
395 | public function getFault()
396 | {
397 | return $this->fault;
398 | }
399 |
400 | /**
401 | * Retrieve method parameters as XMLRPC values
402 | *
403 | * @return array
404 | */
405 | protected function getXmlRpcParams()
406 | {
407 | $params = [];
408 | if (is_array($this->xmlRpcParams)) {
409 | foreach ($this->xmlRpcParams as $param) {
410 | $value = $param['value'];
411 | $type = $param['type'] ?: AbstractValue::AUTO_DETECT_TYPE;
412 |
413 | if (! $value instanceof AbstractValue) {
414 | $value = AbstractValue::getXmlRpcValue($value, $type);
415 | }
416 | $params[] = $value;
417 | }
418 | }
419 |
420 | return $params;
421 | }
422 |
423 | /**
424 | * Create XML request
425 | *
426 | * @return string
427 | */
428 | public function saveXml()
429 | {
430 | $args = $this->getXmlRpcParams();
431 | $method = $this->getMethod();
432 |
433 | $generator = AbstractValue::getGenerator();
434 | $generator->openElement('methodCall')
435 | ->openElement('methodName', $method)
436 | ->closeElement('methodName');
437 |
438 | if (is_array($args) && count($args)) {
439 | $generator->openElement('params');
440 |
441 | foreach ($args as $arg) {
442 | $generator->openElement('param');
443 | $arg->generateXml();
444 | $generator->closeElement('param');
445 | }
446 | $generator->closeElement('params');
447 | }
448 | $generator->closeElement('methodCall');
449 |
450 | return $generator->flush();
451 | }
452 |
453 | /**
454 | * Return XML request
455 | */
456 | public function __toString(): string
457 | {
458 | return $this->saveXML();
459 | }
460 | }
461 |
--------------------------------------------------------------------------------
/src/AbstractValue.php:
--------------------------------------------------------------------------------
1 | type;
116 | }
117 |
118 | /**
119 | * Get XML generator instance
120 | *
121 | * @return GeneratorInterface
122 | */
123 | public static function getGenerator()
124 | {
125 | if (! static::$generator) {
126 | if (extension_loaded('xmlwriter')) {
127 | static::$generator = new Generator\XmlWriter();
128 | } else {
129 | static::$generator = new Generator\DomDocument();
130 | }
131 | }
132 |
133 | return static::$generator;
134 | }
135 |
136 | /**
137 | * Sets XML generator instance
138 | *
139 | * @return void
140 | */
141 | public static function setGenerator(?Generator\GeneratorInterface $generator = null)
142 | {
143 | static::$generator = $generator;
144 | }
145 |
146 | /**
147 | * Changes the encoding of the generator
148 | *
149 | * @param string $encoding
150 | * @return void
151 | */
152 | public static function setEncoding($encoding)
153 | {
154 | $generator = static::getGenerator();
155 | $newGenerator = new $generator($encoding);
156 | static::setGenerator($newGenerator);
157 | }
158 |
159 | /**
160 | * Return the value of this object, convert the XML-RPC native value into a PHP variable
161 | *
162 | * @return mixed
163 | */
164 | abstract public function getValue();
165 |
166 | /**
167 | * Return the XML code that represent a native MXL-RPC value
168 | *
169 | * @return string
170 | */
171 | public function saveXml()
172 | {
173 | if (! $this->xml) {
174 | $this->generateXml();
175 | $this->xml = (string) static::getGenerator();
176 | }
177 | return $this->xml;
178 | }
179 |
180 | /**
181 | * Generate XML code that represent a native XML/RPC value
182 | *
183 | * @return void
184 | */
185 | public function generateXml()
186 | {
187 | $this->generate();
188 | }
189 |
190 | /**
191 | * Creates a Value* object, representing a native XML-RPC value
192 | * A XmlRpcValue object can be created in 3 ways:
193 | * 1. Autodetecting the native type out of a PHP variable
194 | * (if $type is not set or equal to Value::AUTO_DETECT_TYPE)
195 | * 2. By specifying the native type ($type is one of the Value::XMLRPC_TYPE_* constants)
196 | * 3. From a XML string ($type is set to Value::XML_STRING)
197 | *
198 | * By default the value type is autodetected according to it's PHP type
199 | *
200 | * You may optionally pass a bitmask of LIBXML options via the
201 | * $libXmlOptions parameter; as an example, you might use LIBXML_PARSEHUGE.
202 | * See https://www.php.net/manual/en/libxml.constants.php for a full list.
203 | *
204 | * @param mixed $value
205 | * @param self::AUTO_DETECT_TYPE|self::XML* $type
206 | * @param int $libXmlOptions Bitmask of LIBXML options to use for XML * operations
207 | * @throws ValueException
208 | * @return AbstractValue
209 | */
210 | public static function getXmlRpcValue($value, $type = self::AUTO_DETECT_TYPE, int $libXmlOptions = 0)
211 | {
212 | switch ($type) {
213 | case self::AUTO_DETECT_TYPE:
214 | // Auto detect the XML-RPC native type from the PHP type of $value
215 | return static::phpVarToNativeXmlRpc($value);
216 |
217 | case self::XML_STRING:
218 | // Parse the XML string given in $value and get the XML-RPC value in it
219 | return static::xmlStringToNativeXmlRpc($value, $libXmlOptions);
220 |
221 | case self::XMLRPC_TYPE_I4:
222 | // fall through to the next case
223 | case self::XMLRPC_TYPE_INTEGER:
224 | return new Value\Integer($value);
225 |
226 | case self::XMLRPC_TYPE_I8:
227 | // fall through to the next case
228 | case self::XMLRPC_TYPE_APACHEI8:
229 | return self::$USE_BIGINT_FOR_I8 ? new Value\BigInteger($value) : new Value\Integer($value);
230 |
231 | case self::XMLRPC_TYPE_DOUBLE:
232 | return new Value\Double($value);
233 |
234 | case self::XMLRPC_TYPE_BOOLEAN:
235 | return new Value\Boolean($value);
236 |
237 | case self::XMLRPC_TYPE_STRING:
238 | return new Value\Text($value);
239 |
240 | case self::XMLRPC_TYPE_BASE64:
241 | return new Value\Base64($value);
242 |
243 | case self::XMLRPC_TYPE_NIL:
244 | // fall through to the next case
245 | case self::XMLRPC_TYPE_APACHENIL:
246 | return new Value\Nil();
247 |
248 | case self::XMLRPC_TYPE_DATETIME:
249 | return new Value\DateTime($value);
250 |
251 | case self::XMLRPC_TYPE_ARRAY:
252 | return new Value\ArrayValue($value);
253 |
254 | case self::XMLRPC_TYPE_STRUCT:
255 | return new Value\Struct($value);
256 |
257 | default:
258 | throw new ValueException('Given type is not a ' . self::class . ' constant');
259 | }
260 | }
261 |
262 | /**
263 | * Get XML-RPC type for a PHP native variable
264 | *
265 | * @static
266 | * @param mixed $value
267 | * @throws InvalidArgumentException
268 | * @return string
269 | */
270 | public static function getXmlRpcTypeByValue($value)
271 | {
272 | if (is_object($value)) {
273 | if ($value instanceof AbstractValue) {
274 | return $value->getType();
275 | } elseif ($value instanceof DateTime) {
276 | return self::XMLRPC_TYPE_DATETIME;
277 | }
278 | return static::getXmlRpcTypeByValue(get_object_vars($value));
279 | } elseif (is_array($value)) {
280 | if (! empty($value) && is_array($value) && (array_keys($value) !== range(0, count($value) - 1))) {
281 | return self::XMLRPC_TYPE_STRUCT;
282 | }
283 | return self::XMLRPC_TYPE_ARRAY;
284 | } elseif (is_int($value)) {
285 | return $value > PHP_INT_MAX ? self::XMLRPC_TYPE_I8 : self::XMLRPC_TYPE_INTEGER;
286 | } elseif (is_float($value)) {
287 | return self::XMLRPC_TYPE_DOUBLE;
288 | } elseif (is_bool($value)) {
289 | return self::XMLRPC_TYPE_BOOLEAN;
290 | } elseif (null === $value) {
291 | return self::XMLRPC_TYPE_NIL;
292 | } elseif (is_string($value)) {
293 | return self::XMLRPC_TYPE_STRING;
294 | }
295 | throw new InvalidArgumentException(sprintf(
296 | 'No matching XMLRPC type found for php type %s.',
297 | gettype($value)
298 | ));
299 | }
300 |
301 | /**
302 | * Transform a PHP native variable into a XML-RPC native value
303 | *
304 | * @param mixed $value The PHP variable for conversion
305 | * @throws InvalidArgumentException
306 | * @return AbstractValue
307 | * @static
308 | */
309 | protected static function phpVarToNativeXmlRpc($value)
310 | {
311 | // @see https://getlaminas.org/issues/browse/Laminas-8623
312 | if ($value instanceof AbstractValue) {
313 | return $value;
314 | }
315 |
316 | switch (static::getXmlRpcTypeByValue($value)) {
317 | case self::XMLRPC_TYPE_DATETIME:
318 | /** @psalm-suppress MixedArgument */
319 | return new Value\DateTime($value);
320 |
321 | case self::XMLRPC_TYPE_ARRAY:
322 | /** @psalm-suppress MixedArgument */
323 | return new Value\ArrayValue($value);
324 |
325 | case self::XMLRPC_TYPE_STRUCT:
326 | /** @psalm-suppress MixedArgument */
327 | return new Value\Struct($value);
328 |
329 | case self::XMLRPC_TYPE_INTEGER:
330 | /** @psalm-suppress MixedArgument */
331 | return new Value\Integer($value);
332 |
333 | case self::XMLRPC_TYPE_DOUBLE:
334 | /** @psalm-suppress MixedArgument */
335 | return new Value\Double($value);
336 |
337 | case self::XMLRPC_TYPE_BOOLEAN:
338 | /** @psalm-suppress MixedArgument */
339 | return new Value\Boolean($value);
340 |
341 | case self::XMLRPC_TYPE_NIL:
342 | return new Value\Nil();
343 |
344 | case self::XMLRPC_TYPE_STRING:
345 | // Fall through to the next case
346 | default:
347 | // If type isn't identified (or identified as string), it treated as string
348 | /** @psalm-suppress MixedArgument */
349 | return new Value\Text($value);
350 | }
351 | }
352 |
353 | /**
354 | * Transform an XML string into a XML-RPC native value
355 | *
356 | * @param string|SimpleXMLElement $xml A SimpleXMLElement object represent the XML string
357 | * It can be also a valid XML string for conversion
358 | * @throws ValueException
359 | * @return AbstractValue
360 | * @static
361 | */
362 | protected static function xmlStringToNativeXmlRpc($xml, int $libXmlOptions = 0)
363 | {
364 | static::createSimpleXMLElement($xml, $libXmlOptions);
365 |
366 | static::extractTypeAndValue($xml, $type, $value);
367 |
368 | switch ($type) {
369 | // All valid and known XML-RPC native values
370 | case self::XMLRPC_TYPE_I4:
371 | // Fall through to the next case
372 | case self::XMLRPC_TYPE_INTEGER:
373 | $xmlrpcValue = new Value\Integer($value);
374 | break;
375 | case self::XMLRPC_TYPE_APACHEI8:
376 | // Fall through to the next case
377 | case self::XMLRPC_TYPE_I8:
378 | $xmlrpcValue = self::$USE_BIGINT_FOR_I8 ? new Value\BigInteger($value) : new Value\Integer($value);
379 | break;
380 | case self::XMLRPC_TYPE_DOUBLE:
381 | $xmlrpcValue = new Value\Double($value);
382 | break;
383 | case self::XMLRPC_TYPE_BOOLEAN:
384 | $xmlrpcValue = new Value\Boolean($value);
385 | break;
386 | case self::XMLRPC_TYPE_STRING:
387 | $xmlrpcValue = new Value\Text($value);
388 | break;
389 | case self::XMLRPC_TYPE_DATETIME: // The value should already be in an iso8601 format
390 | $xmlrpcValue = new Value\DateTime($value);
391 | break;
392 | case self::XMLRPC_TYPE_BASE64: // The value should already be base64 encoded
393 | $xmlrpcValue = new Value\Base64($value, true);
394 | break;
395 | case self::XMLRPC_TYPE_NIL:
396 | // Fall through to the next case
397 | case self::XMLRPC_TYPE_APACHENIL:
398 | // The value should always be NULL
399 | $xmlrpcValue = new Value\Nil();
400 | break;
401 | case self::XMLRPC_TYPE_ARRAY:
402 | // PHP 5.2.4 introduced a regression in how empty($xml->value)
403 | // returns; need to look for the item specifically
404 | $data = null;
405 | foreach ($value->children() as $key => $value) {
406 | if ('data' === $key) {
407 | $data = $value;
408 | break;
409 | }
410 | }
411 |
412 | if (null === $data) {
413 | throw new ValueException(
414 | 'Invalid XML for XML-RPC native '
415 | . self::XMLRPC_TYPE_ARRAY
416 | . ' type: ARRAY tag must contain DATA tag'
417 | );
418 | }
419 | $values = [];
420 | // Parse all the elements of the array from the XML string
421 | // (simple xml element) to Value objects
422 | foreach ($data->value as $element) {
423 | $values[] = static::xmlStringToNativeXmlRpc($element, $libXmlOptions);
424 | }
425 | $xmlrpcValue = new Value\ArrayValue($values);
426 | break;
427 | case self::XMLRPC_TYPE_STRUCT:
428 | $values = [];
429 | // Parse all the members of the struct from the XML string
430 | // (simple xml element) to Value objects
431 | foreach ($value->member as $member) {
432 | // @todo? If a member doesn't have a tag, we don't add it to the struct
433 | // Maybe we want to throw an exception here ?
434 | if (! isset($member->value) || ! isset($member->name)) {
435 | continue;
436 | }
437 | $values[(string) $member->name] = static::xmlStringToNativeXmlRpc($member->value, $libXmlOptions);
438 | }
439 | $xmlrpcValue = new Value\Struct($values);
440 | break;
441 | default:
442 | throw new ValueException(
443 | 'Value type \'' . $type . '\' parsed from the XML string is not a known XML-RPC native type'
444 | );
445 | }
446 | $xmlrpcValue->setXML($xml->asXML());
447 |
448 | return $xmlrpcValue;
449 | }
450 |
451 | /**
452 | * @param SimpleXMLElement|string $xml
453 | */
454 | protected static function createSimpleXMLElement(&$xml, int $libXmlOptions = 0)
455 | {
456 | if ($xml instanceof SimpleXMLElement) {
457 | return;
458 | }
459 |
460 | try {
461 | $xml = new SimpleXMLElement($xml, $libXmlOptions);
462 | } catch (Exception $e) {
463 | // The given string is not a valid XML
464 | throw new ValueException(
465 | 'Failed to create XML-RPC value from XML string: ' . $e->getMessage(),
466 | $e->getCode(),
467 | $e
468 | );
469 | }
470 | }
471 |
472 | /**
473 | * Extract XML/RPC type and value from SimpleXMLElement object
474 | *
475 | * @param string $type Type bind variable
476 | * @param string $value Value bind variable
477 | * @return void
478 | */
479 | protected static function extractTypeAndValue(SimpleXMLElement $xml, &$type, &$value)
480 | {
481 | // Casting is necessary to work with strict-typed systems
482 | foreach ((array) $xml as $type => $value) {
483 | break;
484 | }
485 | if (! $type && $value === null) {
486 | $namespaces = ['ex' => 'http://ws.apache.org/xmlrpc/namespaces/extensions'];
487 | foreach ($namespaces as $namespaceName => $namespaceUri) {
488 | foreach ((array) $xml->children($namespaceUri) as $type => $value) {
489 | break;
490 | }
491 | if ($type !== null) {
492 | $type = $namespaceName . ':' . $type;
493 | break;
494 | }
495 | }
496 | }
497 |
498 | // If no type was specified, the default is string
499 | if (! is_string($type) || $type === '') {
500 | $type = self::XMLRPC_TYPE_STRING;
501 | if (empty($value) && preg_match('#^.*$#', $xml->asXML())) {
502 | $value = str_replace(['', ''], '', $xml->asXML());
503 | }
504 | }
505 | }
506 |
507 | /**
508 | * @param string $xml
509 | * @return void
510 | */
511 | protected function setXML($xml)
512 | {
513 | $this->xml = static::getGenerator()->stripDeclaration($xml);
514 | }
515 | }
516 |
--------------------------------------------------------------------------------
/src/Server.php:
--------------------------------------------------------------------------------
1 |
35 | * use Laminas\XmlRpc;
36 | *
37 | * // Instantiate server
38 | * $server = new XmlRpc\Server();
39 | *
40 | * // Allow some exceptions to report as fault responses:
41 | * XmlRpc\Server\Fault::attachFaultException('My\\Exception');
42 | * XmlRpc\Server\Fault::attachObserver('My\\Fault\\Observer');
43 | *
44 | * // Get or build dispatch table:
45 | * if (!XmlRpc\Server\Cache::get($filename, $server)) {
46 | *
47 | * // Attach Some_Service_Class in 'some' namespace
48 | * $server->setClass('Some\\Service\\Class', 'some');
49 | *
50 | * // Attach Another_Service_Class in 'another' namespace
51 | * $server->setClass('Another\\Service\\Class', 'another');
52 | *
53 | * // Create dispatch table cache file
54 | * XmlRpc\Server\Cache::save($filename, $server);
55 | * }
56 | *
57 | * $response = $server->handle();
58 | * echo $response;
59 | *
60 | */
61 | class Server extends AbstractServer
62 | {
63 | /**
64 | * Character encoding
65 | *
66 | * @var string
67 | */
68 | protected $encoding = 'UTF-8';
69 |
70 | /**
71 | * Request processed
72 | *
73 | * @var null|Request
74 | */
75 | protected $request;
76 |
77 | /**
78 | * Class to use for responses; defaults to {@link Response\Http}
79 | *
80 | * @var string
81 | */
82 | protected $responseClass = Http::class;
83 |
84 | /**
85 | * Dispatch table of name => method pairs
86 | *
87 | * @var Definition
88 | */
89 | protected $table;
90 |
91 | /**
92 | * PHP types => XML-RPC types
93 | *
94 | * @var array
95 | */
96 | protected $typeMap = [
97 | 'i4' => 'i4',
98 | 'int' => 'int',
99 | 'integer' => 'int',
100 | 'i8' => 'i8',
101 | 'ex:i8' => 'i8',
102 | 'double' => 'double',
103 | 'float' => 'double',
104 | 'real' => 'double',
105 | 'boolean' => 'boolean',
106 | 'bool' => 'boolean',
107 | 'true' => 'boolean',
108 | 'false' => 'boolean',
109 | 'string' => 'string',
110 | 'str' => 'string',
111 | 'base64' => 'base64',
112 | 'dateTime.iso8601' => 'dateTime.iso8601',
113 | 'date' => 'dateTime.iso8601',
114 | 'time' => 'dateTime.iso8601',
115 | DateTime::class => 'dateTime.iso8601',
116 | 'array' => 'array',
117 | 'struct' => 'struct',
118 | 'null' => 'nil',
119 | 'nil' => 'nil',
120 | 'ex:nil' => 'nil',
121 | 'void' => 'void',
122 | 'mixed' => 'struct',
123 | ];
124 |
125 | /**
126 | * Send arguments to all methods or just constructor?
127 | *
128 | * @var bool
129 | */
130 | protected $sendArgumentsToAllMethods = true;
131 |
132 | /**
133 | * Flag: whether or not {@link handle()} should return a response instead
134 | * of automatically emitting it.
135 | *
136 | * @var bool
137 | */
138 | protected $returnResponse = false;
139 |
140 | /**
141 | * Last response results.
142 | *
143 | * @var Response
144 | */
145 | protected $response;
146 |
147 | /**
148 | * @internal
149 | *
150 | * @var Server\System
151 | */
152 | public $system;
153 |
154 | /**
155 | * Constructor
156 | *
157 | * Creates system.* methods.
158 | */
159 | public function __construct()
160 | {
161 | $this->table = new Definition();
162 | $this->registerSystemMethods();
163 | }
164 |
165 | /**
166 | * Proxy calls to system object
167 | *
168 | * @param string $method
169 | * @param array $params
170 | * @return mixed
171 | * @throws Server\Exception\BadMethodCallException
172 | */
173 | public function __call($method, $params)
174 | {
175 | $system = $this->getSystem();
176 | if (! method_exists($system, $method)) {
177 | throw new Server\Exception\BadMethodCallException('Unknown instance method called on server: ' . $method);
178 | }
179 | return call_user_func_array([$system, $method], $params);
180 | }
181 |
182 | /**
183 | * Attach a callback as an XMLRPC method
184 | *
185 | * Attaches a callback as an XMLRPC method, prefixing the XMLRPC method name
186 | * with $namespace, if provided. Reflection is done on the callback's
187 | * docblock to create the methodHelp for the XMLRPC method.
188 | *
189 | * Additional arguments to pass to the function at dispatch may be passed;
190 | * any arguments following the namespace will be aggregated and passed at
191 | * dispatch time.
192 | *
193 | * @param string|array|callable $function Valid callback
194 | * @param string $namespace Optional namespace prefix
195 | * @throws InvalidArgumentException
196 | * @return void
197 | */
198 | public function addFunction($function, $namespace = '')
199 | {
200 | if (! is_string($function) && ! is_array($function)) {
201 | throw new InvalidArgumentException('Unable to attach function; invalid', 611);
202 | }
203 |
204 | $argv = null;
205 | if (2 < func_num_args()) {
206 | $argv = func_get_args();
207 | $argv = array_slice($argv, 2);
208 | }
209 |
210 | $function = (array) $function;
211 | foreach ($function as $func) {
212 | if (! is_string($func) || ! function_exists($func)) {
213 | throw new InvalidArgumentException('Unable to attach function; invalid', 611);
214 | }
215 | $reflection = Reflection::reflectFunction($func, $argv, $namespace);
216 | $this->_buildSignature($reflection);
217 | }
218 | }
219 |
220 | /**
221 | * Attach class methods as XMLRPC method handlers
222 | *
223 | * $class may be either a class name or an object. Reflection is done on the
224 | * class or object to determine the available public methods, and each is
225 | * attached to the server as an available method; if a $namespace has been
226 | * provided, that namespace is used to prefix the XMLRPC method names.
227 | *
228 | * Any additional arguments beyond $namespace will be passed to a method at
229 | * invocation.
230 | *
231 | * @param string|object $class
232 | * @param string $namespace Optional
233 | * @param mixed $argv Optional arguments to pass to methods
234 | * @return void
235 | * @throws InvalidArgumentException On invalid input.
236 | */
237 | public function setClass($class, $namespace = '', $argv = null)
238 | {
239 | if (is_string($class) && ! class_exists($class)) {
240 | throw new InvalidArgumentException('Invalid method class', 610);
241 | }
242 |
243 | if (2 < func_num_args()) {
244 | $argv = func_get_args();
245 | $argv = array_slice($argv, 2);
246 | }
247 |
248 | $dispatchable = Reflection::reflectClass($class, $argv, $namespace);
249 | foreach ($dispatchable->getMethods() as $reflection) {
250 | $this->_buildSignature($reflection, $class);
251 | }
252 | }
253 |
254 | /**
255 | * Raise an xmlrpc server fault
256 | *
257 | * @param string|Exception $fault
258 | * @param int $code
259 | * @return Server\Fault
260 | */
261 | public function fault($fault = null, $code = 404)
262 | {
263 | if (! $fault instanceof Exception) {
264 | $fault = (string) $fault;
265 | if (empty($fault)) {
266 | $fault = 'Unknown Error';
267 | }
268 | $fault = new Server\Exception\RuntimeException($fault, $code);
269 | }
270 |
271 | return Server\Fault::getInstance($fault);
272 | }
273 |
274 | /**
275 | * Set return response flag
276 | *
277 | * If true, {@link handle()} will return the response instead of
278 | * automatically sending it back to the requesting client.
279 | *
280 | * The response is always available via {@link getResponse()}.
281 | *
282 | * @param bool $flag
283 | * @return Server
284 | */
285 | public function setReturnResponse($flag = true)
286 | {
287 | $this->returnResponse = (bool) $flag;
288 | return $this;
289 | }
290 |
291 | /**
292 | * Retrieve return response flag
293 | *
294 | * @return bool
295 | */
296 | public function getReturnResponse()
297 | {
298 | return $this->returnResponse;
299 | }
300 |
301 | /**
302 | * Handle an xmlrpc call
303 | *
304 | * @param Request $request Optional
305 | * @return Response|Fault
306 | */
307 | public function handle($request = false)
308 | {
309 | // Get request
310 | if (
311 | (! $request || ! $request instanceof Request)
312 | && (null === ($request = $this->getRequest()))
313 | ) {
314 | $request = new Request\Http();
315 | $request->setEncoding($this->getEncoding());
316 | }
317 |
318 | $this->setRequest($request);
319 |
320 | if ($request->isFault()) {
321 | $response = $request->getFault();
322 | } else {
323 | try {
324 | $response = $this->handleRequest($request);
325 | } catch (Exception $e) {
326 | $response = $this->fault($e);
327 | }
328 | }
329 |
330 | // Set output encoding
331 | $response->setEncoding($this->getEncoding());
332 | $this->response = $response;
333 |
334 | if (! $this->returnResponse) {
335 | echo $response;
336 | return;
337 | }
338 |
339 | return $response;
340 | }
341 |
342 | /**
343 | * Load methods as returned from {@link getFunctions}
344 | *
345 | * Typically, you will not use this method; it will be called using the
346 | * results pulled from {@link Laminas\XmlRpc\Server\Cache::get()}.
347 | *
348 | * @param array|Definition $definition
349 | * @return void
350 | * @throws InvalidArgumentException On invalid input.
351 | */
352 | public function loadFunctions($definition)
353 | {
354 | if (! is_array($definition) && ! $definition instanceof Definition) {
355 | if (is_object($definition)) {
356 | $type = $definition::class;
357 | } else {
358 | $type = gettype($definition);
359 | }
360 | throw new InvalidArgumentException(
361 | 'Unable to load server definition; must be an array or Laminas\Server\Definition, received ' . $type,
362 | 612
363 | );
364 | }
365 |
366 | $this->table->clearMethods();
367 | $this->registerSystemMethods();
368 |
369 | if ($definition instanceof Definition) {
370 | $definition = $definition->getMethods();
371 | }
372 |
373 | foreach ($definition as $key => $method) {
374 | if (str_starts_with($key, 'system.')) {
375 | continue;
376 | }
377 | $this->table->addMethod($method, $key);
378 | }
379 | }
380 |
381 | /**
382 | * Set encoding
383 | *
384 | * @param string $encoding
385 | * @return Server
386 | */
387 | public function setEncoding($encoding)
388 | {
389 | $this->encoding = $encoding;
390 | AbstractValue::setEncoding($encoding);
391 | return $this;
392 | }
393 |
394 | /**
395 | * Retrieve current encoding
396 | *
397 | * @return string
398 | */
399 | public function getEncoding()
400 | {
401 | return $this->encoding;
402 | }
403 |
404 | /**
405 | * Do nothing; persistence is handled via {@link Laminas\XmlRpc\Server\Cache}
406 | *
407 | * @param mixed $mode
408 | * @return void
409 | */
410 | public function setPersistence($mode)
411 | {
412 | }
413 |
414 | /**
415 | * Set the request object
416 | *
417 | * @param string|Request $request
418 | * @return Server
419 | * @throws InvalidArgumentException On invalid request class or object.
420 | */
421 | public function setRequest($request)
422 | {
423 | if (is_string($request) && class_exists($request)) {
424 | $request = new $request();
425 | if (! $request instanceof Request) {
426 | throw new InvalidArgumentException('Invalid request class');
427 | }
428 | $request->setEncoding($this->getEncoding());
429 | } elseif (! $request instanceof Request) {
430 | throw new InvalidArgumentException('Invalid request object');
431 | }
432 |
433 | $this->request = $request;
434 | return $this;
435 | }
436 |
437 | /**
438 | * Return currently registered request object
439 | *
440 | * @return null|Request
441 | */
442 | public function getRequest()
443 | {
444 | return $this->request;
445 | }
446 |
447 | /**
448 | * Last response.
449 | *
450 | * @return Response
451 | */
452 | public function getResponse()
453 | {
454 | return $this->response;
455 | }
456 |
457 | /**
458 | * Set the class to use for the response
459 | *
460 | * @param string $class
461 | * @throws InvalidArgumentException If invalid response class.
462 | * @return bool True if class was set, false if not
463 | */
464 | public function setResponseClass($class)
465 | {
466 | if (! class_exists($class) || ! is_subclass_of($class, Response::class)) {
467 | throw new InvalidArgumentException('Invalid response class');
468 | }
469 | $this->responseClass = $class;
470 | return true;
471 | }
472 |
473 | /**
474 | * Retrieve current response class
475 | *
476 | * @return string
477 | */
478 | public function getResponseClass()
479 | {
480 | return $this->responseClass;
481 | }
482 |
483 | /**
484 | * Retrieve dispatch table
485 | *
486 | * @return array
487 | */
488 | public function getDispatchTable()
489 | {
490 | return $this->table;
491 | }
492 |
493 | /**
494 | * Returns a list of registered methods
495 | *
496 | * Returns an array of dispatchables (Laminas\Server\Reflection\ReflectionFunction,
497 | * ReflectionMethod, and ReflectionClass items).
498 | *
499 | * @return array
500 | */
501 | public function getFunctions()
502 | {
503 | return $this->table->toArray();
504 | }
505 |
506 | /**
507 | * Retrieve system object
508 | *
509 | * @return Server\System
510 | */
511 | public function getSystem()
512 | {
513 | return $this->system;
514 | }
515 |
516 | /**
517 | * Send arguments to all methods?
518 | *
519 | * If setClass() is used to add classes to the server, this flag defined
520 | * how to handle arguments. If set to true, all methods including constructor
521 | * will receive the arguments. If set to false, only constructor will receive the
522 | * arguments
523 | *
524 | * @param bool|null $flag
525 | * @return self
526 | */
527 | public function sendArgumentsToAllMethods($flag = null)
528 | {
529 | if ($flag === null) {
530 | return $this->sendArgumentsToAllMethods;
531 | }
532 |
533 | $this->sendArgumentsToAllMethods = (bool) $flag;
534 | return $this;
535 | }
536 |
537 | // @codingStandardsIgnoreStart
538 | /**
539 | * Map PHP type to XML-RPC type
540 | *
541 | * @param string $type
542 | * @return string
543 | */
544 | protected function _fixType($type)
545 | {
546 | return $this->typeMap[$type] ?? 'void';
547 | }
548 | // @codingStandardsIgnoreEnd
549 |
550 | /**
551 | * Handle an xmlrpc call (actual work)
552 | *
553 | * @return Response
554 | * @throws Server\Exception\RuntimeException
555 | * Laminas\XmlRpc\Server\Exceptions are thrown for internal errors; otherwise,
556 | * any other exception may be thrown by the callback
557 | */
558 | protected function handleRequest(Request $request)
559 | {
560 | $method = $request->getMethod();
561 |
562 | // Check for valid method
563 | if (! $this->table->hasMethod($method)) {
564 | throw new Server\Exception\RuntimeException('Method "' . $method . '" does not exist', 620);
565 | }
566 |
567 | $info = $this->table->getMethod($method);
568 | $params = $request->getParams();
569 | $argv = $info->getInvokeArguments();
570 | if (0 < count($argv) && $this->sendArgumentsToAllMethods()) {
571 | $params = array_merge($params, $argv);
572 | }
573 |
574 | // Check calling parameters against signatures
575 | $matched = false;
576 | $sigCalled = $request->getTypes();
577 |
578 | $sigLength = count($sigCalled);
579 | $paramsLen = count($params);
580 | if ($sigLength < $paramsLen) {
581 | for ($i = $sigLength; $i < $paramsLen; ++$i) {
582 | $xmlRpcValue = AbstractValue::getXmlRpcValue($params[$i]);
583 | $sigCalled[] = $xmlRpcValue->getType();
584 | }
585 | }
586 |
587 | $signatures = $info->getPrototypes();
588 | foreach ($signatures as $signature) {
589 | $sigParams = $signature->getParameters();
590 | if ($sigCalled === $sigParams) {
591 | $matched = true;
592 | break;
593 | }
594 | }
595 | if (! $matched) {
596 | throw new Server\Exception\RuntimeException('Calling parameters do not match signature', 623);
597 | }
598 |
599 | $return = $this->_dispatch($info, $params);
600 | $responseClass = $this->getResponseClass();
601 | return new $responseClass($return);
602 | }
603 |
604 | /**
605 | * Register system methods with the server
606 | *
607 | * @return void
608 | */
609 | protected function registerSystemMethods()
610 | {
611 | $system = new Server\System($this);
612 | $this->system = $system;
613 | $this->setClass($system, 'system');
614 | }
615 |
616 | /**
617 | * Checks if the object has this class as one of its parents
618 | *
619 | * @deprecated since laminas 2.3 requires PHP >= 5.3.23
620 | *
621 | * @see https://bugs.php.net/bug.php?id=53727
622 | * @see https://github.com/zendframework/zf2/pull/1807
623 | *
624 | * @param string $className
625 | * @param string $type
626 | * @return bool
627 | */
628 | protected static function isSubclassOf($className, $type)
629 | {
630 | return is_subclass_of($className, $type);
631 | }
632 | }
633 |
--------------------------------------------------------------------------------