1<?php
2namespace IXR\Server;
3
4use IXR\DataType\Base64;
5use IXR\DataType\Date;
6use IXR\Message\Error;
7
8/**
9 * IXR_IntrospectionServer
10 *
11 * @package IXR
12 * @since 1.5.0
13 */
14class IntrospectionServer extends Server
15{
16
17    private $signatures;
18    private $help;
19
20    public function __construct()
21    {
22        $this->setCallbacks();
23        $this->setCapabilities();
24        $this->capabilities['introspection'] = [
25            'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
26            'specVersion' => 1
27        ];
28        $this->addCallback(
29            'system.methodSignature',
30            'this:methodSignature',
31            ['array', 'string'],
32            'Returns an array describing the return type and required parameters of a method'
33        );
34        $this->addCallback(
35            'system.getCapabilities',
36            'this:getCapabilities',
37            ['struct'],
38            'Returns a struct describing the XML-RPC specifications supported by this server'
39        );
40        $this->addCallback(
41            'system.listMethods',
42            'this:listMethods',
43            ['array'],
44            'Returns an array of available methods on this server'
45        );
46        $this->addCallback(
47            'system.methodHelp',
48            'this:methodHelp',
49            ['string', 'string'],
50            'Returns a documentation string for the specified method'
51        );
52    }
53
54    public function addCallback($method, $callback, $args, $help)
55    {
56        $this->callbacks[$method] = $callback;
57        $this->signatures[$method] = $args;
58        $this->help[$method] = $help;
59    }
60
61    public function call($methodname, $args)
62    {
63        // Make sure it's in an array
64        if ($args && !is_array($args)) {
65            $args = [$args];
66        }
67
68        // Over-rides default call method, adds signature check
69        if (!$this->hasMethod($methodname)) {
70            return new Error(-32601,
71                'server error. requested method "' . $this->message->methodName . '" not specified.');
72        }
73        $method = $this->callbacks[$methodname];
74        $signature = $this->signatures[$methodname];
75        array_shift($signature);
76
77        // Check the number of arguments
78        if (count($args) != count($signature)) {
79            return new Error(-32602, 'server error. wrong number of method parameters');
80        }
81
82        // Check the argument types
83        $ok = true;
84        $argsbackup = $args;
85        for ($i = 0, $j = count($args); $i < $j; $i++) {
86            $arg = array_shift($args);
87            $type = array_shift($signature);
88            switch ($type) {
89                case 'int':
90                case 'i4':
91                    if (is_array($arg) || !is_int($arg)) {
92                        $ok = false;
93                    }
94                    break;
95                case 'base64':
96                case 'string':
97                    if (!is_string($arg)) {
98                        $ok = false;
99                    }
100                    break;
101                case 'boolean':
102                    if ($arg !== false && $arg !== true) {
103                        $ok = false;
104                    }
105                    break;
106                case 'float':
107                case 'double':
108                    if (!is_float($arg)) {
109                        $ok = false;
110                    }
111                    break;
112                case 'date':
113                case 'dateTime.iso8601':
114                    if (!($arg instanceof Date)) {
115                        $ok = false;
116                    }
117                    break;
118            }
119            if (!$ok) {
120                return new Error(-32602, 'server error. invalid method parameters');
121            }
122        }
123        // It passed the test - run the "real" method call
124        return parent::call($methodname, $argsbackup);
125    }
126
127    public function methodSignature($method)
128    {
129        if (!$this->hasMethod($method)) {
130            return new Error(-32601, 'server error. requested method "' . $method . '" not specified.');
131        }
132        // We should be returning an array of types
133        $types = $this->signatures[$method];
134        $return = [];
135        foreach ($types as $type) {
136            switch ($type) {
137                case 'string':
138                    $return[] = 'string';
139                    break;
140                case 'int':
141                case 'i4':
142                    $return[] = 42;
143                    break;
144                case 'double':
145                    $return[] = 3.1415;
146                    break;
147                case 'dateTime.iso8601':
148                    $return[] = new Date(time());
149                    break;
150                case 'boolean':
151                    $return[] = true;
152                    break;
153                case 'base64':
154                    $return[] = new Base64('base64');
155                    break;
156                case 'array':
157                    $return[] = ['array'];
158                    break;
159                case 'struct':
160                    $return[] = ['struct' => 'struct'];
161                    break;
162            }
163        }
164        return $return;
165    }
166
167    public function methodHelp($method)
168    {
169        return $this->help[$method];
170    }
171}
172