no change, just nicer php
[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 PLCAPI($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 ) { break; }
85     }
86     return $msg;
87   }
88
89   function error_log($error_msg, $backtrace_level = 1) {
90     $backtrace = debug_backtrace();
91     $file = $backtrace[$backtrace_level]['file'];
92     $line = $backtrace[$backtrace_level]['line'];
93
94     $error_line='PLCAPI error:  ' . $error_msg ;
95     if ($file) $error_line .= ' in file ' . $file;
96     if ($line) $error_line .= ' on line ' . $line;
97     $this->errors[] = $error_line;
98     # TODO: setup a config variable for more detailed stack traces, for API errors.
99     if ( TRUE ){
100       error_log($error_line);
101     } else {
102        error_log($this->backtrace_php());
103     }
104   }
105
106   function error() {
107     if (empty($this->trace)) {
108       return NULL;
109     } else {
110       $last_trace = end($this->trace);
111       return implode("\\n", $last_trace['errors']);
112     }
113   }
114
115   function trace() {
116     return $this->trace;
117   }
118
119   function microtime_float() {
120     list($usec, $sec) = explode(" ", microtime());
121     return ((float) $usec + (float) $sec);
122   }
123
124   function call($method, $args = NULL) {
125     if ($this->multicall) {
126       $this->calls[] = array ('methodName' => $method,
127                                 'params' => $args);
128       return NULL;
129     } else {
130       return $this->internal_call($method, $args, 3);
131     }
132   }
133
134   /*
135    * Use PhpXmlRpc\Value before encoding the request
136    */
137   function xmlrpcValue($value) {
138       switch(gettype($value)) {
139           case 'array':
140               $members = array();
141               foreach($value as $vk => $vv) {
142                   $members[$vk] = $this->xmlrpcValue($vv);
143               }
144
145               if ((array_key_exists(0, $value)) || (empty($value))) {
146                   return new PhpXmlRpc\Value(
147                       $members,
148                       'array'
149                   );
150               } else {
151                   return new PhpXmlRpc\Value(
152                       $members,
153                       'struct'
154                   );
155               }
156
157               break;
158           case 'double':
159               return new PhpXmlRpc\Value($value, 'double');
160               break;
161           case 'boolean':
162               return new PhpXmlRpc\Value($value, 'boolean');
163               break;
164           case 'NULL':
165           case 'null':
166               return new PhpXmlRpc\Value(null, 'null');
167               break;
168           case 'integer':
169               return new PhpXmlRpc\Value($value, 'int');
170               break;
171           default:
172               return new PhpXmlRpc\Value($value);
173               break;
174       }
175   }
176
177     function internal_call($method, $args = NULL, $backtrace_level = 2) {
178         if (class_exists('PhpXmlRpc\\PhpXmlRpc')) {
179             return $this->internal_call_phpxmlrpc($method, $args, $backtrace_level);
180         } else {
181             return $this->internal_call_xmlrpc($method, $args, $backtrace_level);
182         }
183     }
184
185   /*
186    * the new internal call, will use PhpXmlRpc
187    */
188   function internal_call_phpxmlrpc($method, $args = NULL, $backtrace_level = 2) {
189 //
190     //      echo '<pre>';
191     //      var_dump($method);
192     //      var_dump($args);
193     //      echo '</pre>';
194
195       PhpXmlRpc\PhpXmlRpc::$xmlrpc_null_extension = true;
196
197       if ($this->port == 443) {
198           $url = 'https://';
199       } else {
200           $url = 'http://';
201       }
202
203       // Set the URL for the request
204       $url .= $this->server . ':' . $this->port . '/' . $this->path;
205
206       $client = new PhpXmlRpc\Client($url);
207       $client->setSSLVerifyPeer(false);
208       /*
209        * 1 -> not verify CN
210        * 2 -> verify CN (default)
211        */
212       $client->setSSLVerifyHost(1);
213
214       $values = $this->xmlrpcValue($args);
215
216       $response = $client->send(new PhpXmlRpc\Request($method, $values));
217
218
219       if (!$response->faultCode()) {
220           $encoder = new PhpXmlRpc\Encoder();
221           $v = $encoder->decode($response->value());
222
223           return $v;
224       } else {
225           $this->error_log("An error occurred [" . $response->faultCode() . "] ".
226               $response->faultString());
227           return NULL;
228       }
229   }
230
231   /*
232    * The original internal call that uses php XML-RPC
233    */
234   function internal_call_xmlrpc($method, $args = NULL, $backtrace_level = 2) {
235     $curl = curl_init();
236
237     // Verify peer certificate if talking over SSL
238     if ($this->port == 443) {
239       curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 2);
240       if (!empty($this->cainfo)) {
241         curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
242       } elseif (defined('PLC_API_CA_SSL_CRT')) {
243         curl_setopt($curl, CURLOPT_CAINFO, PLC_API_CA_SSL_CRT);
244       }
245       $url = 'https://';
246     } else {
247       $url = 'http://';
248     }
249
250     // Set the URL for the request
251     $url .= $this->server . ':' . $this->port . '/' . $this->path;
252     curl_setopt($curl, CURLOPT_URL, $url);
253
254     // Marshal the XML-RPC request as a POST variable. <nil/> is an
255     // extension to the XML-RPC spec that is supported in our custom
256     // version of xmlrpc.so via the 'allow_null' output_encoding key.
257     $request = xmlrpc_encode_request($method, $args, array('null_extension'));
258     curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
259
260     // Construct the HTTP header
261     $header[] = 'Content-type: text/xml';
262     $header[] = 'Content-length: ' . strlen($request);
263     curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
264
265     // Set some miscellaneous options
266     curl_setopt($curl, CURLOPT_TIMEOUT, 180);
267
268     // Get the output of the request
269     curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
270     $t0 = $this->microtime_float();
271     $output = curl_exec($curl);
272     $t1 = $this->microtime_float();
273
274     if (curl_errno($curl)) {
275       $this->error_log('curl: ' . curl_error($curl), true);
276       $ret = NULL;
277     } else {
278       $ret = xmlrpc_decode($output);
279       if (is_array($ret) && xmlrpc_is_fault($ret)) {
280         $this->error_log('Fault Code ' . $ret['faultCode'] . ': ' .
281                          $ret['faultString'], $backtrace_level, true);
282         $ret = NULL;
283       }
284     }
285
286     curl_close($curl);
287
288     $this->trace[] = array('method' => $method,
289                            'args' => $args,
290                            'runtime' => $t1 - $t0,
291                            'return' => $ret,
292                            'errors' => $this->errors);
293     $this->errors = array();
294
295     return $ret;
296   }
297
298   function begin() {
299     if (!empty($this->calls)) {
300       $this->error_log ('Warning: multicall already in progress');
301     }
302
303     $this->multicall = true;
304   }
305
306   function xmlrpc_is_fault($arr) {
307         // check if xmlrpc_is_fault exists
308         return is_array($arr) && array_key_exists('faultCode', $arr) && array_key_exists('faultString', $arr);
309     }
310
311   function commit() {
312     if (!empty ($this->calls)) {
313       $ret = array();
314       $results = $this->internal_call('system.multicall', array ($this->calls));
315       foreach ($results as $result) {
316         if (is_array($result)) {
317           if ($this->xmlrpc_is_fault($result)) {
318             $this->error_log('Fault Code ' . $result['faultCode'] . ': ' .
319                              $result['faultString'], 1, true);
320             $ret[] = NULL;
321             // Thierry - march 30 2007
322             // using $adm->error() is broken with begin/commit style
323             // this is because error() uses last item in trace and checks for ['errors']
324             // when using begin/commit we do run internal_call BUT internal_call checks for
325             // multicall's result globally, not individual results, so ['errors'] comes empty
326             // I considered hacking internal_call
327             // to *NOT* maintain this->trace at all when invoked with multicall
328             // but it is too complex to get all values right
329             // so let's go for the hacky way, and just record individual errors at the right place
330             $this->trace[count($this->trace)-1]['errors'][] = end($this->errors);
331           } else {
332             $ret[] = $result[0];
333           }
334         } else {
335           $ret[] = $result;
336         }
337       }
338     } else {
339       $ret = NULL;
340     }
341
342     $this->calls = array();
343     $this->multicall = false;
344
345     return $ret;
346   }
347
348   //
349   // PLCAPI Methods
350   //
351
352   function __call($name, $args) {
353      array_unshift($args, $this->auth);
354      return $this->call($name, $args);
355   }
356 }
357
358 global $adm;
359
360 $adm = new PLCAPI(array('AuthMethod' => "capability",
361                         'Username' => PLC_API_MAINTENANCE_USER,
362                         'AuthString' => PLC_API_MAINTENANCE_PASSWORD));
363
364 ?>