1<?php
2namespace IXR\Client;
3
4use IXR\Message\Error;
5use IXR\Message\Message;
6use IXR\Request\Request;
7
8/**
9 * IXR_Client
10 *
11 * @package IXR
12 * @since   1.5.0
13 *
14 */
15class Client
16{
17    protected $server;
18    protected $port;
19    protected $path;
20    protected $useragent;
21    protected $response;
22    /** @var bool|Message */
23    protected $message = false;
24    protected $debug = false;
25    /** @var int Connection timeout in seconds */
26    protected $timeout;
27    /** @var null|int Timeout for actual data transfer; in seconds */
28    protected $timeout_io = null;
29    protected $headers = [];
30
31    /**
32     * @var null|Error
33     *
34     * Storage place for an error message
35     */
36    private $error = null;
37
38    public function __construct($server, $path = false, $port = 80, $timeout = 15, $timeout_io = null)
39    {
40        if (!$path) {
41            // Assume we have been given a URL instead
42            $bits = parse_url($server);
43            $this->server = $bits['host'];
44            $this->port = isset($bits['port']) ? $bits['port'] : 80;
45            $this->path = isset($bits['path']) ? $bits['path'] : '/';
46
47            // Make absolutely sure we have a path
48            if (!$this->path) {
49                $this->path = '/';
50            }
51
52            if (!empty($bits['query'])) {
53                $this->path .= '?' . $bits['query'];
54            }
55        } else {
56            $this->server = $server;
57            $this->path = $path;
58            $this->port = $port;
59        }
60        $this->useragent = 'The Incutio XML-RPC PHP Library';
61        $this->timeout = $timeout;
62        $this->timeout_io = $timeout_io;
63    }
64
65    public function query()
66    {
67        $args = func_get_args();
68        $method = array_shift($args);
69        $request = new Request($method, $args);
70        $length = $request->getLength();
71        $xml = $request->getXml();
72        $r = "\r\n";
73        $request = "POST {$this->path} HTTP/1.0$r";
74
75        // Merged from WP #8145 - allow custom headers
76        $this->headers['Host'] = $this->server;
77        $this->headers['Content-Type'] = 'text/xml';
78        $this->headers['User-Agent'] = $this->useragent;
79        $this->headers['Content-Length'] = $length;
80
81        foreach ($this->headers as $header => $value) {
82            $request .= "{$header}: {$value}{$r}";
83        }
84        $request .= $r;
85
86        $request .= $xml;
87
88        // Now send the request
89        if ($this->debug) {
90            echo '<pre class="ixr_request">' . htmlspecialchars($request) . "\n</pre>\n\n";
91        }
92
93        if ($this->timeout) {
94            try {
95                $fp = fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
96            } catch (\Exception $e) {
97                $fp = false;
98            }
99        } else {
100            try {
101                $fp = fsockopen($this->server, $this->port, $errno, $errstr);
102            } catch (\Exception $e) {
103                $fp = false;
104            }
105        }
106        if (!$fp) {
107            return $this->handleError(-32300, 'transport error - could not open socket');
108        }
109        if (null !== $this->timeout_io) {
110            stream_set_timeout($fp, $this->timeout_io);
111        }
112        fputs($fp, $request);
113        $contents = '';
114        $debugContents = '';
115        $gotFirstLine = false;
116        $gettingHeaders = true;
117        while (!feof($fp)) {
118            $line = fgets($fp, 4096);
119            if (!$gotFirstLine) {
120                // Check line for '200'
121                if (strstr($line, '200') === false) {
122                    return $this->handleError(-32300, 'transport error - HTTP status code was not 200');
123                }
124                $gotFirstLine = true;
125            }
126            if (trim($line) == '') {
127                $gettingHeaders = false;
128            }
129            if (!$gettingHeaders) {
130                // merged from WP #12559 - remove trim
131                $contents .= $line;
132            }
133            if ($this->debug) {
134                $debugContents .= $line;
135            }
136        }
137        if ($this->debug) {
138            echo '<pre class="ixr_response">' . htmlspecialchars($debugContents) . "\n</pre>\n\n";
139        }
140
141        // Now parse what we've got back
142        $this->message = new Message($contents);
143        if (!$this->message->parse()) {
144            // XML error
145            return $this->handleError(-32700, 'Parse error. Message not well formed');
146        }
147
148        // Is the message a fault?
149        if ($this->message->messageType == 'fault') {
150            return $this->handleError($this->message->faultCode, $this->message->faultString);
151        }
152
153        // Message must be OK
154        return true;
155    }
156
157    public function getResponse()
158    {
159        // methodResponses can only have one param - return that
160        return $this->message->params[0];
161    }
162
163    public function isError()
164    {
165        return (is_object($this->error));
166    }
167
168    protected function handleError($errorCode, $errorMessage)
169    {
170        $this->error = new Error($errorCode, $errorMessage);
171
172        return false;
173    }
174
175    public function getError()
176    {
177        return $this->error;
178    }
179
180    public function getErrorCode()
181    {
182        return $this->error->code;
183    }
184
185    public function getErrorMessage()
186    {
187        return $this->error->message;
188    }
189
190
191    /**
192     * Gets the current timeout set for data transfer
193     * @return int|null
194     */
195    public function getTimeoutIo()
196    {
197        return $this->timeout_io;
198    }
199
200    /**
201     * Sets the timeout for data transfer
202     * @param int $timeout_io
203     * @return $this
204     */
205    public function setTimeoutIo($timeout_io)
206    {
207        $this->timeout_io = $timeout_io;
208    }
209}
210