Merge pull request #3 from Smarre/master
[plcapi.git] / lib / xmlrpcval.php
1 <?php
2
3 class xmlrpcval {
4
5     /// @todo: does these need to be public?
6     public $me=array();
7     public $mytype=0;
8     public $_php_class=null;
9
10     /**
11       * @param mixed $val
12       * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed
13       */
14     function __construct($val=-1, $type='') {
15         /// @todo: optimization creep - do not call addXX, do it all inline.
16         /// downside: booleans will not be coerced anymore
17         if($val!==-1 || $type!='')
18         {
19             // optimization creep: inlined all work done by constructor
20             switch($type)
21             {
22                 case '':
23                     $this->mytype=1;
24                     $this->me['string']=$val;
25                     break;
26                 case 'i4':
27                 case 'int':
28                 case 'double':
29                 case 'string':
30                 case 'boolean':
31                 case 'dateTime.iso8601':
32                 case 'base64':
33                 case 'null':
34                     $this->mytype=1;
35                     $this->me[$type]=$val;
36                     break;
37                 case 'array':
38                     $this->mytype=2;
39                     $this->me['array']=$val;
40                     break;
41                 case 'struct':
42                     $this->mytype=3;
43                     $this->me['struct']=$val;
44                     break;
45                 default:
46                     error_log("XML-RPC: ".__METHOD__.": not a known type ($type)");
47             }
48             /*if($type=='')
49             {
50                 $type='string';
51             }
52             if($GLOBALS['xmlrpcTypes'][$type]==1)
53             {
54                 $this->addScalar($val,$type);
55             }
56             elseif($GLOBALS['xmlrpcTypes'][$type]==2)
57             {
58                 $this->addArray($val);
59             }
60             elseif($GLOBALS['xmlrpcTypes'][$type]==3)
61             {
62                 $this->addStruct($val);
63             }*/
64         }
65     }
66
67     /**
68       * Add a single php value to an (unitialized) xmlrpcval
69       * @param mixed $val
70       * @param string $type
71       * @return int 1 or 0 on failure
72       */
73     function addScalar($val, $type='string')
74     {
75         $xmlrpc = Phpxmlrpc::instance();
76
77         $typeof = null;
78         if(isset($xmlrpc->xmlrpcTypes[$type])) {
79             $typeof = $xmlrpc->xmlrpcTypes[$type];
80         }
81
82         if($typeof!=1)
83         {
84             error_log("XML-RPC: ".__METHOD__.": not a scalar type ($type)");
85             return 0;
86         }
87
88         // coerce booleans into correct values
89         // NB: we should either do it for datetimes, integers and doubles, too,
90         // or just plain remove this check, implemented on booleans only...
91         if($type==$xmlrpc->xmlrpcBoolean)
92         {
93             if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
94             {
95                 $val=true;
96             }
97             else
98             {
99                 $val=false;
100             }
101         }
102
103         switch($this->mytype)
104         {
105             case 1:
106                 error_log('XML-RPC: '.__METHOD__.': scalar xmlrpcval can have only one value');
107                 return 0;
108             case 3:
109                 error_log('XML-RPC: '.__METHOD__.': cannot add anonymous scalar to struct xmlrpcval');
110                 return 0;
111             case 2:
112                 // we're adding a scalar value to an array here
113                 //$ar=$this->me['array'];
114                 //$ar[]=new xmlrpcval($val, $type);
115                 //$this->me['array']=$ar;
116                 // Faster (?) avoid all the costly array-copy-by-val done here...
117                 $this->me['array'][]=new xmlrpcval($val, $type);
118                 return 1;
119             default:
120                 // a scalar, so set the value and remember we're scalar
121                 $this->me[$type]=$val;
122                 $this->mytype=$typeof;
123                 return 1;
124         }
125     }
126
127     /**
128       * Add an array of xmlrpcval objects to an xmlrpcval
129       * @param array $vals
130       * @return int 1 or 0 on failure
131       *
132       * @todo add some checking for $vals to be an array of xmlrpcvals?
133       */
134     public function addArray($vals)
135     {
136         $xmlrpc = Phpxmlrpc::instance();
137         if($this->mytype==0)
138         {
139             $this->mytype=$xmlrpc->xmlrpcTypes['array'];
140             $this->me['array']=$vals;
141             return 1;
142         }
143         elseif($this->mytype==2)
144         {
145             // we're adding to an array here
146             $this->me['array'] = array_merge($this->me['array'], $vals);
147             return 1;
148         }
149         else
150         {
151             error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
152             return 0;
153         }
154     }
155
156     /**
157      * Add an array of named xmlrpcval objects to an xmlrpcval
158      * @param array $vals
159      * @return int 1 or 0 on failure
160      *
161      * @todo add some checking for $vals to be an array?
162      */
163     public function addStruct($vals)
164     {
165         $xmlrpc = Phpxmlrpc::instance();
166
167         if($this->mytype==0)
168         {
169             $this->mytype=$xmlrpc->xmlrpcTypes['struct'];
170             $this->me['struct']=$vals;
171             return 1;
172         }
173         elseif($this->mytype==3)
174         {
175             // we're adding to a struct here
176             $this->me['struct'] = array_merge($this->me['struct'], $vals);
177             return 1;
178         }
179         else
180         {
181             error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
182             return 0;
183         }
184     }
185
186     /**
187      * Returns a string containing "struct", "array" or "scalar" describing the base type of the value
188      * @return string
189      */
190     public function kindOf()
191     {
192         switch($this->mytype)
193         {
194             case 3:
195                 return 'struct';
196                 break;
197             case 2:
198                 return 'array';
199                 break;
200             case 1:
201                 return 'scalar';
202                 break;
203             default:
204                 return 'undef';
205         }
206     }
207
208     private function serializedata($typ, $val, $charset_encoding='')
209     {
210         $xmlrpc = Phpxmlrpc::instance();
211         $rs='';
212
213         if(!isset($xmlrpc->xmlrpcTypes[$typ])) {
214             return $rs;
215         }
216
217         switch($xmlrpc->xmlrpcTypes[$typ])
218         {
219             case 1:
220                 switch($typ)
221                 {
222                     case $xmlrpc->xmlrpcBase64:
223                         $rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
224                         break;
225                     case $xmlrpc->xmlrpcBoolean:
226                         $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
227                         break;
228                     case $xmlrpc->xmlrpcString:
229                         // G. Giunta 2005/2/13: do NOT use htmlentities, since
230                         // it will produce named html entities, which are invalid xml
231                         $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $xmlrpc->xmlrpc_internalencoding, $charset_encoding). "</${typ}>";
232                         break;
233                     case $xmlrpc->xmlrpcInt:
234                     case $xmlrpc->xmlrpcI4:
235                         $rs.="<${typ}>".(int)$val."</${typ}>";
236                         break;
237                     case $xmlrpc->xmlrpcDouble:
238                         // avoid using standard conversion of float to string because it is locale-dependent,
239                         // and also because the xmlrpc spec forbids exponential notation.
240                         // sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
241                         // The code below tries its best at keeping max precision while avoiding exp notation,
242                         // but there is of course no limit in the number of decimal places to be used...
243                         $rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', ''))."</${typ}>";
244                         break;
245                     case $xmlrpc->xmlrpcDateTime:
246                         if (is_string($val))
247                         {
248                             $rs.="<${typ}>${val}</${typ}>";
249                         }
250                         else if(is_a($val, 'DateTime'))
251                         {
252                             $rs.="<${typ}>".$val->format('Ymd\TH:i:s')."</${typ}>";
253                         }
254                         else if(is_int($val))
255                         {
256                             $rs.="<${typ}>".strftime("%Y%m%dT%H:%M:%S", $val)."</${typ}>";
257                         }
258                         else
259                         {
260                             // not really a good idea here: but what shall we output anyway? left for backward compat...
261                             $rs.="<${typ}>${val}</${typ}>";
262                         }
263                         break;
264                     case $xmlrpc->xmlrpcNull:
265                         if ($xmlrpc->xmlrpc_null_apache_encoding)
266                         {
267                             $rs.="<ex:nil/>";
268                         }
269                         else
270                         {
271                             $rs.="<nil/>";
272                         }
273                         break;
274                     default:
275                         // no standard type value should arrive here, but provide a possibility
276                         // for xmlrpcvals of unknown type...
277                         $rs.="<${typ}>${val}</${typ}>";
278                 }
279                 break;
280             case 3:
281                 // struct
282                 if ($this->_php_class)
283                 {
284                     $rs.='<struct php_class="' . $this->_php_class . "\">\n";
285                 }
286                 else
287                 {
288                     $rs.="<struct>\n";
289                 }
290                 foreach($val as $key2 => $val2)
291                 {
292                     $rs.='<member><name>'.xmlrpc_encode_entitites($key2, $xmlrpc->xmlrpc_internalencoding, $charset_encoding)."</name>\n";
293                     //$rs.=$this->serializeval($val2);
294                     $rs.=$val2->serialize($charset_encoding);
295                     $rs.="</member>\n";
296                 }
297                 $rs.='</struct>';
298                 break;
299             case 2:
300                 // array
301                 $rs.="<array>\n<data>\n";
302                 for($i=0; $i<count($val); $i++)
303                 {
304                     //$rs.=$this->serializeval($val[$i]);
305                     $rs.=$val[$i]->serialize($charset_encoding);
306                 }
307                 $rs.="</data>\n</array>";
308                 break;
309             default:
310                 break;
311         }
312         return $rs;
313     }
314
315     /**
316      * Returns xml representation of the value. XML prologue not included
317      * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
318      * @return string
319      */
320     public function serialize($charset_encoding='')
321     {
322         // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
323         //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
324         //{
325             reset($this->me);
326             list($typ, $val) = each($this->me);
327             return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n";
328         //}
329     }
330
331     // DEPRECATED
332     function serializeval($o)
333     {
334         // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
335         //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
336         //{
337             $ar=$o->me;
338             reset($ar);
339             list($typ, $val) = each($ar);
340             return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
341         //}
342     }
343
344     /**
345      * Checks whether a struct member with a given name is present.
346      * Works only on xmlrpcvals of type struct.
347      * @param string $m the name of the struct member to be looked up
348      * @return boolean
349      */
350     public function structmemexists($m)
351     {
352         return array_key_exists($m, $this->me['struct']);
353     }
354
355     /**
356      * Returns the value of a given struct member (an xmlrpcval object in itself).
357      * Will raise a php warning if struct member of given name does not exist
358      * @param string $m the name of the struct member to be looked up
359      * @return xmlrpcval
360      */
361     public function structmem($m)
362     {
363         return $this->me['struct'][$m];
364     }
365
366     /**
367      * Reset internal pointer for xmlrpcvals of type struct.
368      */
369     public function structreset()
370     {
371         reset($this->me['struct']);
372     }
373
374     /**
375      * Return next member element for xmlrpcvals of type struct.
376      * @return xmlrpcval
377      */
378     public function structeach()
379     {
380         return each($this->me['struct']);
381     }
382
383     // DEPRECATED! this code looks like it is very fragile and has not been fixed
384     // for a long long time. Shall we remove it for 2.0?
385     function getval()
386     {
387         // UNSTABLE
388         reset($this->me);
389         list($a,$b)=each($this->me);
390         // contributed by I Sofer, 2001-03-24
391         // add support for nested arrays to scalarval
392         // i've created a new method here, so as to
393         // preserve back compatibility
394
395         if(is_array($b))
396         {
397             @reset($b);
398             while(list($id,$cont) = @each($b))
399             {
400                 $b[$id] = $cont->scalarval();
401             }
402         }
403
404         // add support for structures directly encoding php objects
405         if(is_object($b))
406         {
407             $t = get_object_vars($b);
408             @reset($t);
409             while(list($id,$cont) = @each($t))
410             {
411                 $t[$id] = $cont->scalarval();
412             }
413             @reset($t);
414             while(list($id,$cont) = @each($t))
415             {
416                 @$b->$id = $cont;
417             }
418         }
419         // end contrib
420         return $b;
421     }
422
423     /**
424      * Returns the value of a scalar xmlrpcval
425      * @return mixed
426      */
427     public function scalarval()
428     {
429         reset($this->me);
430         list(,$b)=each($this->me);
431         return $b;
432     }
433
434     /**
435      * Returns the type of the xmlrpcval.
436      * For integers, 'int' is always returned in place of 'i4'
437      * @return string
438      */
439     public function scalartyp()
440     {
441         $xmlrpc = Phpxmlrpc::instance();
442
443         reset($this->me);
444         list($a,)=each($this->me);
445         if($a==$xmlrpc->xmlrpcI4)
446         {
447             $a=$xmlrpc->xmlrpcInt;
448         }
449         return $a;
450     }
451
452     /**
453      * Returns the m-th member of an xmlrpcval of struct type
454      * @param integer $m the index of the value to be retrieved (zero based)
455      * @return xmlrpcval
456      */
457     public function arraymem($m)
458     {
459         return $this->me['array'][$m];
460     }
461
462     /**
463      * Returns the number of members in an xmlrpcval of array type
464      * @return integer
465      */
466     public function arraysize()
467     {
468         return count($this->me['array']);
469     }
470
471     /**
472      * Returns the number of members in an xmlrpcval of struct type
473      * @return integer
474      */
475     public function structsize()
476     {
477         return count($this->me['struct']);
478     }
479 }
480
481 ?>