the PLCAPI class requires __construct as well in order to move to php8
[plcapi.git] / php / plc_api.php
1 <?php
2 //
3 // PlanetLab Central Slice API (PLCAPI) PHP interface
4 //
5 // DO NOT EDIT. This file was automatically generated at
6 // @DATE@.
7 //
8 // Mark Huang <mlhuang@cs.princeton.edu>
9 // Copyright (C) 2005-2006 The Trustees of Princeton University
10 //
11
12 //ini_set('error_reporting', 1);
13
14 /*
15  * May 2017 - Ciro Scognamiglio <c.scognamiglio@cslash.net>
16  *
17  * xmlrpc php module is not compatible anymore with the PLCAPI class,
18  * if the package phpxmlrpc is installed in the same dir it will be used instead
19  *
20  * https://github.com/gggeek/phpxmlrpc
21  *
22  * If the package is not found the php module XML-RPC is used if available
23  *
24  */
25
26 if (file_exists(__DIR__ . '/phpxmlrpc/src/Autoloader.php')) {
27     include_once __DIR__ . '/phpxmlrpc/src/Autoloader.php';
28     PhpXmlRpc\Autoloader::register();
29 }
30
31 require_once 'plc_config.php';
32
33 class PLCAPI {
34
35     var $auth;
36     var $server;
37     var $port;
38     var $path;
39     var $errors;
40     var $trace;
41     var $calls;
42     var $multicall;
43
44     function __construct($auth = NULL,
45                          $server = PLC_API_HOST,
46                          $port = PLC_API_PORT,
47                          $path = PLC_API_PATH,
48                          $cainfo = NULL) {
49         $this->auth = $auth;
50         $this->server = $server;
51         $this->port = $port;
52         $this->path = $path;
53         $this->cainfo = $cainfo;
54         $this->errors = array();
55         $this->trace = array();
56         $this->calls = array();
57         $this->multicall = false;
58     }
59
60     function rec_join ($arg) {
61         if ( is_array($arg) ) {
62             $ret = "";
63             foreach ( $arg as $i ) {
64                 $l = $this->rec_join($i);
65                 # ignore html code.
66                 if ( $l[0] != "<" ) { $ret .= $l . ", "; }
67             }
68             return $ret;
69         } else {
70             settype($arg, "string");
71             return $arg;
72         }
73     }
74
75     function backtrace_php () {
76         $backtrace = debug_backtrace();
77         $msg = "";
78         $len = count($backtrace);
79         $cnt = 1;
80         foreach( array_reverse($backtrace) as $line ) {
81             $msg .= "File '". $line['file'] . "' line " . $line['line'] . "\n";
82             $msg .= "    " . $line['function'] . "( "  . $this->rec_join($line['args']) . ")\n";
83             $cnt += 1;
84             if ($cnt == $len)
85                 break;
86         }
87         return $msg;
88     }
89
90     function error_log($error_msg, $backtrace_level = 1) {
91         $backtrace = debug_backtrace();
92         $file = $backtrace[$backtrace_level]['file'];
93         $line = $backtrace[$backtrace_level]['line'];
94
95         $error_line='PLCAPI error:  ' . $error_msg ;
96         if ($file)
97             $error_line .= ' in file ' . $file;
98         if ($line)
99             $error_line .= ' on line ' . $line;
100         $this->errors[] = $error_line;
101         # TODO: setup a config variable for more detailed stack traces, for API errors.
102         if (TRUE) {
103           error_log($error_line);
104         } else {
105            error_log($this->backtrace_php());
106         }
107     }
108
109     function error() {
110         if (empty($this->trace)) {
111           return NULL;
112         } else {
113           $last_trace = end($this->trace);
114           return implode("\\n", $last_trace['errors']);
115         }
116     }
117
118     function trace() {
119         return $this->trace;
120     }
121
122     function microtime_float() {
123         list($usec, $sec) = explode(" ", microtime());
124         return ((float) $usec + (float) $sec);
125     }
126
127     function call($method, $args = NULL) {
128         if ($this->multicall) {
129           $this->calls[] = array ('methodName' => $method,
130                                     'params' => $args);
131           return NULL;
132         } else {
133           return $this->internal_call($method, $args, 3);
134         }
135     }
136
137     /*
138     * Use PhpXmlRpc\Value before encoding the request
139     */
140     function xmlrpcValue($value) {
141         switch(gettype($value)) {
142             case 'array':
143                 $members = array();
144                 foreach($value as $vk => $vv) {
145                     $members[$vk] = $this->xmlrpcValue($vv);
146                 }
147
148                 if ((array_key_exists(0, $value)) || (empty($value))) {
149                     return new PhpXmlRpc\Value($members, 'array');
150                 } else {
151                     return new PhpXmlRpc\Value($members, 'struct');
152                 }
153
154                 break;
155             case 'double':
156                 return new PhpXmlRpc\Value($value, 'double');
157                 break;
158             case 'boolean':
159                 return new PhpXmlRpc\Value($value, 'boolean');
160                 break;
161             case 'NULL':
162             case 'null':
163                 return new PhpXmlRpc\Value(null, 'null');
164                 break;
165             case 'integer':
166                 return new PhpXmlRpc\Value($value, 'int');
167                 break;
168             default:
169                 return new PhpXmlRpc\Value($value);
170                 break;
171         }
172     }
173
174     function internal_call($method, $args = NULL, $backtrace_level = 2) {
175         if (class_exists('PhpXmlRpc\\PhpXmlRpc')) {
176             return $this->internal_call_phpxmlrpc($method, $args, $backtrace_level);
177         } else {
178             return $this->internal_call_xmlrpc($method, $args, $backtrace_level);
179         }
180     }
181
182     /*
183      * the new internal call, will use PhpXmlRpc
184      */
185     function internal_call_phpxmlrpc($method, $args = NULL, $backtrace_level = 2) {
186
187 //        echo '<pre>method and args:<br/>';
188 //        var_dump($method);
189 //        var_dump($args);
190 //        echo '</pre>';
191
192         PhpXmlRpc\PhpXmlRpc::$xmlrpc_null_extension = true;
193
194         if ($this->port == 443) {
195             $url = 'https://';
196         } else {
197             $url = 'http://';
198         }
199
200         // Set the URL for the request
201         $url .= $this->server . ':' . $this->port . '/' . $this->path;
202
203         $client = new PhpXmlRpc\Client($url);
204         $client->setSSLVerifyPeer(false);
205         /*
206         * 1 -> not verify CN
207         * 2 -> verify CN (default)
208         */
209         $client->setSSLVerifyHost(1);
210
211         $values = $this->xmlrpcValue($args);
212
213         $response = $client->send(new PhpXmlRpc\Request($method, $values));
214
215 //        echo '<pre>response:<br/>';
216 //        var_dump($response);
217 //        echo '</pre>';
218 //        echo '<pre>response->value():<br/>';
219 //        var_dump($response->value());
220 //        echo '</pre>';
221
222         if (!$response->faultCode()) {
223             $encoder = new PhpXmlRpc\Encoder();
224             $v = $encoder->decode($response->value());
225
226             return $v;
227         } else {
228             $this->error_log(
229                 "An error occurred [" . $response->faultCode() . "] "
230                 . $response->faultString());
231             return NULL;
232         }
233     }
234
235     /*
236     * The original internal call that uses php XML-RPC
237     */
238     function internal_call_xmlrpc($method, $args = NULL, $backtrace_level = 2) {
239         $curl = curl_init();
240
241         // Verify peer certificate if talking over SSL
242         if ($this->port == 443) {
243             curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 2);
244             if (!empty($this->cainfo)) {
245                 curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
246             } elseif (defined('PLC_API_CA_SSL_CRT')) {
247                 curl_setopt($curl, CURLOPT_CAINFO, PLC_API_CA_SSL_CRT);
248             }
249             $url = 'https://';
250         } else {
251             $url = 'http://';
252         }
253
254         // Set the URL for the request
255         $url .= $this->server . ':' . $this->port . '/' . $this->path;
256         curl_setopt($curl, CURLOPT_URL, $url);
257
258         // Marshal the XML-RPC request as a POST variable. <nil/> is an
259         // extension to the XML-RPC spec that is supported in our custom
260         // version of xmlrpc.so via the 'allow_null' output_encoding key.
261         $request = xmlrpc_encode_request($method, $args, array('null_extension'));
262         curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
263
264         // Construct the HTTP header
265         $header[] = 'Content-type: text/xml';
266         $header[] = 'Content-length: ' . strlen($request);
267         curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
268
269         // Set some miscellaneous options
270         curl_setopt($curl, CURLOPT_TIMEOUT, 180);
271
272         // Get the output of the request
273         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
274         $t0 = $this->microtime_float();
275         $output = curl_exec($curl);
276         $t1 = $this->microtime_float();
277
278         if (curl_errno($curl)) {
279             $this->error_log('curl: ' . curl_error($curl), true);
280             $ret = NULL;
281         } else {
282             $ret = xmlrpc_decode($output);
283             if (is_array($ret) && xmlrpc_is_fault($ret)) {
284             $this->error_log('Fault Code ' . $ret['faultCode'] . ': ' .
285                              $ret['faultString'], $backtrace_level, true);
286             $ret = NULL;
287             }
288         }
289
290         curl_close($curl);
291
292         $this->trace[] = array('method' => $method,
293                                'args' => $args,
294                                'runtime' => $t1 - $t0,
295                                'return' => $ret,
296                                'errors' => $this->errors);
297         $this->errors = array();
298
299         return $ret;
300     }
301
302     function begin() {
303         if (!empty($this->calls)) {
304             $this->error_log ('Warning: multicall already in progress');
305         }
306
307         $this->multicall = true;
308     }
309
310     function xmlrpc_is_fault($arr) {
311         // check if xmlrpc_is_fault exists
312         return is_array($arr)
313             && array_key_exists('faultCode', $arr)
314             && array_key_exists('faultString', $arr);
315     }
316
317     function commit() {
318         if (!empty ($this->calls)) {
319             $ret = array();
320             $results = $this->internal_call('system.multicall', array ($this->calls));
321             foreach ($results as $result) {
322                 if (is_array($result)) {
323                     if ($this->xmlrpc_is_fault($result)) {
324                     $this->error_log('Fault Code ' . $result['faultCode'] . ': '
325                                      . $result['faultString'], 1, true);
326                     $ret[] = NULL;
327                     // Thierry - march 30 2007
328                     // using $adm->error() is broken with begin/commit style
329                     // this is because error() uses last item in trace and checks for ['errors']
330                     // when using begin/commit we do run internal_call BUT internal_call checks for
331                     // multicall's result globally, not individual results, so ['errors'] comes empty
332                     // I considered hacking internal_call
333                     // to *NOT* maintain this->trace at all when invoked with multicall
334                     // but it is too complex to get all values right
335                     // so let's go for the hacky way, and just record individual errors at the right place
336                     $this->trace[count($this->trace)-1]['errors'][] = end($this->errors);
337                     } else {
338                         $ret[] = $result[0];
339                     }
340                 } else {
341                     $ret[] = $result;
342                 }
343             }
344         } else {
345             $ret = NULL;
346         }
347
348         $this->calls = array();
349         $this->multicall = false;
350
351         return $ret;
352     }
353
354     //
355     // PLCAPI Methods
356     //
357
358     function __call($name, $args) {
359         array_unshift($args, $this->auth);
360         return $this->call($name, $args);
361     }
362 }
363
364 global $adm;
365
366 $adm = new PLCAPI(array('AuthMethod' => "capability",
367                         'Username' => PLC_API_MAINTENANCE_USER,
368                         'AuthString' => PLC_API_MAINTENANCE_PASSWORD));
369
370 ?>