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