Passed
Push — master ( 67ed62...5da465 )
by Gaetano
02:56
created

Value::structeach()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 1
c 1
b 1
f 0
nc 1
nop 0
dl 0
loc 5
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Charset;
6
use PhpXmlRpc\Helper\Logger;
7
8
/**
9
 * This class enables the creation of values for XML-RPC, by encapsulating plain php values.
10
 */
11
class Value implements \Countable, \IteratorAggregate, \ArrayAccess
12
{
13
    public static $xmlrpcI4 = "i4";
14
    public static $xmlrpcI8 = "i8";
15
    public static $xmlrpcInt = "int";
16
    public static $xmlrpcBoolean = "boolean";
17
    public static $xmlrpcDouble = "double";
18
    public static $xmlrpcString = "string";
19
    public static $xmlrpcDateTime = "dateTime.iso8601";
20
    public static $xmlrpcBase64 = "base64";
21
    public static $xmlrpcArray = "array";
22
    public static $xmlrpcStruct = "struct";
23
    public static $xmlrpcValue = "undefined";
24
    public static $xmlrpcNull = "null";
25
26
    public static $xmlrpcTypes = array(
27
        "i4" => 1,
28
        "i8" => 1,
29
        "int" => 1,
30
        "boolean" => 1,
31
        "double" => 1,
32
        "string" => 1,
33
        "dateTime.iso8601" => 1,
34
        "base64" => 1,
35
        "array" => 2,
36
        "struct" => 3,
37
        "null" => 1,
38
    );
39
40
    /// @todo: do these need to be public?
41
    /** @var Value[]|mixed */
42
    public $me = array();
43
    /** @var int $mytype */
44
    public $mytype = 0;
45
    /** @var string|null $_php_class */
46
    public $_php_class = null;
47
48
    /**
49
     * Build an xmlrpc value.
50
     *
51
     * When no value or type is passed in, the value is left uninitialized, and the value can be added later.
52
     *
53
     * @param mixed $val if passing in an array, all array elements should be PhpXmlRpc\Value themselves
54
     * @param string $type any valid xmlrpc type name (lowercase): i4, int, boolean, string, double, dateTime.iso8601,
55
     *                     base64, array, struct, null.
56
     *                     If null, 'string' is assumed.
57
     *                     You should refer to http://www.xmlrpc.com/spec for more information on what each of these mean.
58
     */
59 636
    public function __construct($val = -1, $type = '')
60
    {
61
        // optimization creep - do not call addXX, do it all inline.
62
        // downside: booleans will not be coerced anymore
63 636
        if ($val !== -1 || $type != '') {
64 635
            switch ($type) {
65 635
                case '':
66 206
                    $this->mytype = 1;
67 206
                    $this->me['string'] = $val;
68 206
                    break;
69 635
                case 'i4':
70 635
                case 'i8':
71 635
                case 'int':
72 594
                case 'double':
73 575
                case 'string':
74 404
                case 'boolean':
75 385
                case 'dateTime.iso8601':
76 366
                case 'base64':
77 347
                case 'null':
78 612
                    $this->mytype = 1;
79 612
                    $this->me[$type] = $val;
80 612
                    break;
81 346
                case 'array':
82 217
                    $this->mytype = 2;
83 217
                    $this->me['array'] = $val;
84 217
                    break;
85 247
                case 'struct':
86 247
                    $this->mytype = 3;
87 247
                    $this->me['struct'] = $val;
88 247
                    break;
89
                default:
90
                    Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
91
            }
92
        }
93 636
    }
94
95
    /**
96
     * Add a single php value to an xmlrpc value.
97
     *
98
     * If the xmlrpc value is an array, the php value is added as its last element.
99
     * If the xmlrpc value is empty (uninitialized), this method makes it a scalar value, and sets that value.
100
     * Fails if the xmlrpc value is not an array and already initialized.
101
     *
102
     * @param mixed $val
103
     * @param string $type allowed values: i4, i8, int, boolean, string, double, dateTime.iso8601, base64, null.
104
     *
105
     * @return int 1 or 0 on failure
106
     */
107 1
    public function addScalar($val, $type = 'string')
108
    {
109 1
        $typeOf = null;
110 1
        if (isset(static::$xmlrpcTypes[$type])) {
111 1
            $typeOf = static::$xmlrpcTypes[$type];
112
        }
113
114 1
        if ($typeOf !== 1) {
115
            Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)");
116
            return 0;
117
        }
118
119
        // coerce booleans into correct values
120
        // NB: we should either do it for datetimes, integers, i8 and doubles, too,
121
        // or just plain remove this check, implemented on booleans only...
122 1
        if ($type == static::$xmlrpcBoolean) {
123
            if (strcasecmp($val, 'true') == 0 || $val == 1 || ($val == true && strcasecmp($val, 'false'))) {
124
                $val = true;
125
            } else {
126
                $val = false;
127
            }
128
        }
129
130 1
        switch ($this->mytype) {
131 1
            case 1:
132
                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value');
133
                return 0;
134 1
            case 3:
135 1
                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value');
136 1
                return 0;
137
            case 2:
138
                // we're adding a scalar value to an array here
139
                $this->me['array'][] = new Value($val, $type);
140
141
                return 1;
142
            default:
143
                // a scalar, so set the value and remember we're scalar
144
                $this->me[$type] = $val;
145
                $this->mytype = $typeOf;
146
147
                return 1;
148
        }
149
    }
150
151
    /**
152
     * Add an array of xmlrpc value objects to an xmlrpc value.
153
     *
154
     * If the xmlrpc value is an array, the elements are appended to the existing ones.
155
     * If the xmlrpc value is empty (uninitialized), this method makes it an array value, and sets that value.
156
     * Fails otherwise.
157
     *
158
     * @param Value[] $values
159
     *
160
     * @return int 1 or 0 on failure
161
     *
162
     * @todo add some checking for $values to be an array of xmlrpc values?
163
     */
164 1
    public function addArray($values)
165
    {
166 1
        if ($this->mytype == 0) {
167
            $this->mytype = static::$xmlrpcTypes['array'];
168
            $this->me['array'] = $values;
169
170
            return 1;
171 1
        } elseif ($this->mytype == 2) {
172
            // we're adding to an array here
173 1
            $this->me['array'] = array_merge($this->me['array'], $values);
174
175 1
            return 1;
176
        } else {
177
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
178
            return 0;
179
        }
180
    }
181
182
    /**
183
     * Merges an array of named xmlrpc value objects into an xmlrpc value.
184
     *
185
     * If the xmlrpc value is a struct, the elements are merged with the existing ones (overwriting existing ones).
186
     * If the xmlrpc value is empty (uninitialized), this method makes it a struct value, and sets that value.
187
     * Fails otherwise.
188
     *
189
     * @param Value[] $values
190
     *
191
     * @return int 1 or 0 on failure
192
     *
193
     * @todo add some checking for $values to be an array?
194
     */
195 1
    public function addStruct($values)
196
    {
197 1
        if ($this->mytype == 0) {
198
            $this->mytype = static::$xmlrpcTypes['struct'];
199
            $this->me['struct'] = $values;
200
201
            return 1;
202 1
        } elseif ($this->mytype == 3) {
203
            // we're adding to a struct here
204 1
            $this->me['struct'] = array_merge($this->me['struct'], $values);
205
206 1
            return 1;
207
        } else {
208
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
209
            return 0;
210
        }
211
    }
212
213
    /**
214
     * Returns a string containing either "struct", "array", "scalar" or "undef", describing the base type of the value.
215
     *
216
     * @return string
217
     */
218 549
    public function kindOf()
219
    {
220 549
        switch ($this->mytype) {
221 549
            case 3:
222 135
                return 'struct';
223 530
            case 2:
224 193
                return 'array';
225 510
            case 1:
226 510
                return 'scalar';
227
            default:
228
                return 'undef';
229
        }
230
    }
231
232 598
    protected function serializedata($typ, $val, $charsetEncoding = '')
233
    {
234 598
        $rs = '';
235
236 598
        if (!isset(static::$xmlrpcTypes[$typ])) {
237 2
            return $rs;
238
        }
239
240 598
        switch (static::$xmlrpcTypes[$typ]) {
241 598
            case 1:
242
                switch ($typ) {
243 579
                    case static::$xmlrpcBase64:
244 20
                        $rs .= "<${typ}>" . base64_encode($val) . "</${typ}>";
245 20
                        break;
246 579
                    case static::$xmlrpcBoolean:
247 40
                        $rs .= "<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
248 40
                        break;
249 560
                    case static::$xmlrpcString:
250
                        // Do NOT use htmlentities, since it will produce named html entities, which are invalid xml
251 479
                        $rs .= "<${typ}>" . Charset::instance()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</${typ}>";
252 479
                        break;
253 408
                    case static::$xmlrpcInt:
254 60
                    case static::$xmlrpcI4:
255 60
                    case static::$xmlrpcI8:
256 369
                        $rs .= "<${typ}>" . (int)$val . "</${typ}>";
257 369
                        break;
258 60
                    case static::$xmlrpcDouble:
259
                        // avoid using standard conversion of float to string because it is locale-dependent,
260
                        // and also because the xmlrpc spec forbids exponential notation.
261
                        // sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
262
                        // The code below tries its best at keeping max precision while avoiding exp notation,
263
                        // but there is of course no limit in the number of decimal places to be used...
264 21
                        $rs .= "<${typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, 128, '.', '')) . "</${typ}>";
265 21
                        break;
266 41
                    case static::$xmlrpcDateTime:
267 21
                        if (is_string($val)) {
268 21
                            $rs .= "<${typ}>${val}</${typ}>";
269 20
                        } elseif (is_a($val, 'DateTime')) {
270 20
                            $rs .= "<${typ}>" . $val->format('Ymd\TH:i:s') . "</${typ}>";
271 20
                        } elseif (is_int($val)) {
272 20
                            $rs .= "<${typ}>" . strftime("%Y%m%dT%H:%M:%S", $val) . "</${typ}>";
273
                        } else {
274
                            // not really a good idea here: but what shall we output anyway? left for backward compat...
275
                            $rs .= "<${typ}>${val}</${typ}>";
276
                        }
277 21
                        break;
278 21
                    case static::$xmlrpcNull:
279 21
                        if (PhpXmlRpc::$xmlrpc_null_apache_encoding) {
280 21
                            $rs .= "<ex:nil/>";
281
                        } else {
282 1
                            $rs .= "<nil/>";
283
                        }
284 21
                        break;
285
                    default:
286
                        // no standard type value should arrive here, but provide a possibility
287
                        // for xmlrpc values of unknown type...
288
                        $rs .= "<${typ}>${val}</${typ}>";
289
                }
290 579
                break;
291 196
            case 3:
292
                // struct
293 119
                if ($this->_php_class) {
294 1
                    $rs .= '<struct php_class="' . $this->_php_class . "\">\n";
295
                } else {
296 119
                    $rs .= "<struct>\n";
297
                }
298 119
                $charsetEncoder = Charset::instance();
299
                /** @var Value $val2 */
300 119
                foreach ($val as $key2 => $val2) {
301 100
                    $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n";
302
                    //$rs.=$this->serializeval($val2);
303 100
                    $rs .= $val2->serialize($charsetEncoding);
304 100
                    $rs .= "</member>\n";
305
                }
306 119
                $rs .= '</struct>';
307 119
                break;
308 138
            case 2:
309
                // array
310 138
                $rs .= "<array>\n<data>\n";
311
                /** @var Value $element */
312 138
                foreach ($val as $element) {
313
                    //$rs.=$this->serializeval($val[$i]);
314 138
                    $rs .= $element->serialize($charsetEncoding);
315
                }
316 138
                $rs .= "</data>\n</array>";
317 138
                break;
318
            default:
319
                break;
320
        }
321
322 598
        return $rs;
323
    }
324
325
    /**
326
     * Returns the xml representation of the value. XML prologue not included.
327
     *
328
     * @param string $charsetEncoding the charset to be used for serialization. if null, US-ASCII is assumed
329
     *
330
     * @return string
331
     */
332 598
    public function serialize($charsetEncoding = '')
333
    {
334 598
        $val = reset($this->me);
335 598
        $typ = key($this->me);
336
337 598
        return '<value>' . $this->serializedata($typ, $val, $charsetEncoding) . "</value>\n";
338
    }
339
340
    /**
341
     * Checks whether a struct member with a given name is present.
342
     *
343
     * Works only on xmlrpc values of type struct.
344
     *
345
     * @param string $key the name of the struct member to be looked up
346
     *
347
     * @return boolean
348
     *
349
     * @deprecated use array access, e.g. isset($val[$key])
350
     */
351 2
    public function structmemexists($key)
352
    {
353
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
354
355 2
        return array_key_exists($key, $this->me['struct']);
356
    }
357
358
    /**
359
     * Returns the value of a given struct member (an xmlrpc value object in itself).
360
     * Will raise a php warning if struct member of given name does not exist.
361
     *
362
     * @param string $key the name of the struct member to be looked up
363
     *
364
     * @return Value
365
     *
366
     * @deprecated use array access, e.g. $val[$key]
367
     */
368 27
    public function structmem($key)
369
    {
370
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
371
372 27
        return $this->me['struct'][$key];
373
    }
374
375
    /**
376
     * Reset internal pointer for xmlrpc values of type struct.
377
     * @deprecated iterate directly over the object using foreach instead
378
     */
379
    public function structreset()
380
    {
381
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
382
383
        reset($this->me['struct']);
384
    }
385
386
    /**
387
     * Return next member element for xmlrpc values of type struct.
388
     *
389
     * @return Value
390
     * @throws \Error starting with php 8.0, this function should not be used, as it will always throw
391
     *
392
     * @deprecated iterate directly over the object using foreach instead
393
     */
394
    public function structeach()
395
    {
396
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
397
398
        return @each($this->me['struct']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return @each($this->me['struct']) returns the type array which is incompatible with the documented return type PhpXmlRpc\Value.
Loading history...
Deprecated Code introduced by
The function each() has been deprecated: 7.2 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

398
        return @/** @scrutinizer ignore-deprecated */ each($this->me['struct']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
399
    }
400
401
    /**
402
     * Returns the value of a scalar xmlrpc value (base 64 decoding is automatically handled here)
403
     *
404
     * @return mixed
405
     */
406 561
    public function scalarval()
407
    {
408 561
        $b = reset($this->me);
409
410 561
        return $b;
411
    }
412
413
    /**
414
     * Returns the type of the xmlrpc value.
415
     *
416
     * For integers, 'int' is always returned in place of 'i4'. 'i8' is considered a separate type and returned as such
417
     *
418
     * @return string
419
     */
420 392
    public function scalartyp()
421
    {
422 392
        reset($this->me);
423 392
        $a = key($this->me);
424 392
        if ($a == static::$xmlrpcI4) {
425
            $a = static::$xmlrpcInt;
426
        }
427
428 392
        return $a;
429
    }
430
431
    /**
432
     * Returns the m-th member of an xmlrpc value of array type.
433
     *
434
     * @param integer $key the index of the value to be retrieved (zero based)
435
     *
436
     * @return Value
437
     *
438
     * @deprecated use array access, e.g. $val[$key]
439
     */
440 39
    public function arraymem($key)
441
    {
442
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
443
444 39
        return $this->me['array'][$key];
445
    }
446
447
    /**
448
     * Returns the number of members in an xmlrpc value of array type.
449
     *
450
     * @return integer
451
     *
452
     * @deprecated use count() instead
453
     */
454 40
    public function arraysize()
455
    {
456
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
457
458 40
        return count($this->me['array']);
459
    }
460
461
    /**
462
     * Returns the number of members in an xmlrpc value of struct type.
463
     *
464
     * @return integer
465
     *
466
     * @deprecated use count() instead
467
     */
468 21
    public function structsize()
469
    {
470
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
471
472 21
        return count($this->me['struct']);
473
    }
474
475
    /**
476
     * Returns the number of members in an xmlrpc value:
477
     * - 0 for uninitialized values
478
     * - 1 for scalar values
479
     * - the number of elements for struct and array values
480
     *
481
     * @return integer
482
     */
483 22
    public function count()
484
    {
485 22
        switch ($this->mytype) {
486 22
            case 3:
487
                return count($this->me['struct']);
488 22
            case 2:
489 22
                return count($this->me['array']);
490
            case 1:
491
                return 1;
492
            default:
493
                return 0;
494
        }
495
    }
496
497
    /**
498
     * Implements the IteratorAggregate interface
499
     *
500
     * @return \ArrayIterator
501
     */
502 233
    public function getIterator() {
503 233
        switch ($this->mytype) {
504 233
            case 3:
505 59
                return new \ArrayIterator($this->me['struct']);
506 194
            case 2:
507 194
                return new \ArrayIterator($this->me['array']);
508
            case 1:
509
                return new \ArrayIterator($this->me);
510
            default:
511
                return new \ArrayIterator();
512
        }
513
    }
514
515 20
    public function offsetSet($offset, $value) {
516
517 20
        switch ($this->mytype) {
518 20
            case 3:
519
                if (!($value instanceof \PhpXmlRpc\Value)) {
520
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Struct');
521
                }
522
                if (is_null($offset)) {
523
                    // disallow struct members with empty names
524
                    throw new \Exception('It is not possible to add anonymous members to an XML-RPC Struct');
525
                } else {
526
                    $this->me['struct'][$offset] = $value;
527
                }
528
                return;
529 20
            case 2:
530 20
                if (!($value instanceof \PhpXmlRpc\Value)) {
531
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Array');
532
                }
533 20
                if (is_null($offset)) {
534 20
                    $this->me['array'][] = $value;
535
                } else {
536
                    // nb: we are not checking that $offset is above the existing array range...
537
                    $this->me['array'][$offset] = $value;
538
                }
539 20
                return;
540
            case 1:
541
// todo: handle i4 vs int
542
                reset($this->me);
543
                $type = key($this->me);
544
                if ($type != $offset) {
545
                    throw new \Exception('');
546
                }
547
                $this->me[$type] = $value;
548
                return;
549
            default:
550
                // it would be nice to allow empty values to be be turned into non-empty ones this way, but we miss info to do so
551
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be set using array index");
552
        }
553
    }
554
555
    public function offsetExists($offset) {
556
        switch ($this->mytype) {
557
            case 3:
558
                return isset($this->me['struct'][$offset]);
559
            case 2:
560
                return isset($this->me['array'][$offset]);
561
            case 1:
562
// todo: handle i4 vs int
563
                return $offset == $this->scalartyp();
564
            default:
565
                return false;
566
        }
567
    }
568
569
    public function offsetUnset($offset) {
570
        switch ($this->mytype) {
571
            case 3:
572
                unset($this->me['struct'][$offset]);
573
                return;
574
            case 2:
575
                unset($this->me['array'][$offset]);
576
                return;
577
            case 1:
578
                // can not remove value from a scalar
579
                throw new \Exception("XML-RPC Value is of type 'scalar' and its value can not be unset using array index");
580
            default:
581
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be unset using array index");
582
        }
583
    }
584
585 139
    public function offsetGet($offset) {
586 139
        switch ($this->mytype) {
587 139
            case 3:
588 138
                return isset($this->me['struct'][$offset]) ? $this->me['struct'][$offset] : null;
589 21
            case 2:
590 21
                return isset($this->me['array'][$offset]) ? $this->me['array'][$offset] : null;
591
            case 1:
592
// on bad type: null or exception?
593
                $value = reset($this->me);
594
                $type = key($this->me);
595
                return $type == $offset ? $value : null;
596
            default:
597
// return null or exception?
598
                throw new \Exception("XML-RPC Value is of type 'undef' and can not be accessed using array index");
599
        }
600
    }
601
}
602