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