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