xref: /dokuwiki/vendor/kissifrot/php-ixr/src/Server/Server.php (revision 678ae4f08c1f4a1f54fc11de91ae69d96377d33e)
1<?php
2
3namespace IXR\Server;
4
5
6use IXR\DataType\Value;
7use IXR\Exception\ServerException;
8use IXR\Message\Error;
9use IXR\Message\Message;
10
11class Server
12{
13    protected $callbacks = [];
14    protected $message;
15    protected $capabilities;
16
17    /**
18     * @throws ServerException
19     */
20    public function __construct($callbacks = false, $data = false, $wait = false)
21    {
22        $this->setCapabilities();
23        if ($callbacks) {
24            $this->callbacks = $callbacks;
25        }
26        $this->setCallbacks();
27        if (!$wait) {
28            $this->serve($data);
29        }
30    }
31
32    /**
33     * @throws ServerException
34     */
35    public function serve($data = false)
36    {
37        if (!$data) {
38            if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
39                header('Content-Type: text/plain'); // merged from WP #9093
40                throw new ServerException('XML-RPC server accepts POST requests only.');
41            }
42
43            $data = file_get_contents('php://input');
44        }
45        $this->message = new Message($data);
46        if (!$this->message->parse()) {
47            $this->error(-32700, 'parse error. not well formed');
48        }
49        if ($this->message->messageType != 'methodCall') {
50            $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
51        }
52        $result = $this->call($this->message->methodName, $this->message->params);
53
54        // Is the result an error?
55        if ($result instanceof Error) {
56            $this->error($result);
57        }
58
59        // Encode the result
60        $r = new Value($result);
61        $resultxml = $r->getXml();
62
63        // Create the XML
64        $xml = <<<EOD
65<methodResponse>
66  <params>
67    <param>
68      <value>
69      $resultxml
70      </value>
71    </param>
72  </params>
73</methodResponse>
74
75EOD;
76        // Send it
77        $this->output($xml);
78    }
79
80    protected function call($methodname, $args)
81    {
82        if (!$this->hasMethod($methodname)) {
83            return new Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.');
84        }
85        $method = $this->callbacks[$methodname];
86        // Perform the callback and send the response
87
88        if (is_array($args) && count($args) == 1) {
89            // If only one parameter just send that instead of the whole array
90            $args = $args[0];
91        }
92
93        try {
94            // Are we dealing with a function or a method?
95            if (is_string($method) && substr($method, 0, 5) === 'this:') {
96                // It's a class method - check it exists
97                $method = substr($method, 5);
98
99                return $this->$method($args);
100            }
101
102            return call_user_func($method, $args);
103        } catch (\BadFunctionCallException $exception) {
104            return new Error(-32601, "server error. requested callable '{$method}' does not exist.");
105        }
106
107    }
108
109    public function error($error, $message = false)
110    {
111        // Accepts either an error object or an error code and message
112        if ($message && !is_object($error)) {
113            $error = new Error($error, $message);
114        }
115        $this->output($error->getXml());
116    }
117
118    public function output($xml)
119    {
120        $xml = '<?xml version="1.0"?>' . "\n" . $xml;
121        $length = strlen($xml);
122        header('Connection: close');
123        header('Content-Length: ' . $length);
124        header('Content-Type: text/xml');
125        header('Date: ' . date('r'));
126        echo $xml;
127        exit;
128    }
129
130    protected function hasMethod($method)
131    {
132        return in_array($method, array_keys($this->callbacks));
133    }
134
135    protected function setCapabilities()
136    {
137        // Initialises capabilities array
138        $this->capabilities = [
139            'xmlrpc' => [
140                'specUrl' => 'http://www.xmlrpc.com/spec',
141                'specVersion' => 1
142            ],
143            'faults_interop' => [
144                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
145                'specVersion' => 20010516
146            ],
147            'system.multicall' => [
148                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
149                'specVersion' => 1
150            ],
151        ];
152    }
153
154    public function getCapabilities($args)
155    {
156        return $this->capabilities;
157    }
158
159    public function setCallbacks()
160    {
161        $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
162        $this->callbacks['system.listMethods'] = 'this:listMethods';
163        $this->callbacks['system.multicall'] = 'this:multiCall';
164    }
165
166    public function listMethods($args)
167    {
168        // Returns a list of methods - uses array_reverse to ensure user defined
169        // methods are listed before server defined methods
170        return array_reverse(array_keys($this->callbacks));
171    }
172
173    public function multiCall($methodcalls)
174    {
175        // See http://www.xmlrpc.com/discuss/msgReader$1208
176        $return = [];
177        foreach ($methodcalls as $call) {
178            $method = $call['methodName'];
179            $params = $call['params'];
180            if ($method == 'system.multicall') {
181                $result = new Error(-32600, 'Recursive calls to system.multicall are forbidden');
182            } else {
183                $result = $this->call($method, $params);
184            }
185            if ($result instanceof Error) {
186                $return[] = [
187                    'faultCode' => $result->code,
188                    'faultString' => $result->message
189                ];
190            } else {
191                $return[] = [$result];
192            }
193        }
194        return $return;
195    }
196}
197