Passed
Push — master ( b6cd05...9f5262 )
by Gaetano
05:39
created

Value::arraymem()   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
nc 1
nop 1
dl 0
loc 5
ccs 0
cts 0
cp 0
crap 2
rs 10
c 1
b 1
f 0
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Traits\CharsetEncoderAware;
6
use PhpXmlRpc\Traits\LoggerAware;
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
    use CharsetEncoderAware;
14
    use LoggerAware;
15
16
    public static $xmlrpcI4 = "i4";
17
    public static $xmlrpcI8 = "i8";
18
    public static $xmlrpcInt = "int";
19
    public static $xmlrpcBoolean = "boolean";
20
    public static $xmlrpcDouble = "double";
21
    public static $xmlrpcString = "string";
22
    public static $xmlrpcDateTime = "dateTime.iso8601";
23
    public static $xmlrpcBase64 = "base64";
24
    public static $xmlrpcArray = "array";
25
    public static $xmlrpcStruct = "struct";
26
    public static $xmlrpcValue = "undefined";
27
    public static $xmlrpcNull = "null";
28
29
    public static $xmlrpcTypes = array(
30
        "i4" => 1,
31
        "i8" => 1,
32
        "int" => 1,
33
        "boolean" => 1,
34
        "double" => 1,
35
        "string" => 1,
36
        "dateTime.iso8601" => 1,
37
        "base64" => 1,
38
        "array" => 2,
39
        "struct" => 3,
40
        "null" => 1,
41
    );
42
43
    /// @todo: do these need to be public?
44
    /** @var Value[]|mixed */
45
    public $me = array();
46
    /**
47
     * @var int
48
     * @internal
49
     */
50
    public $mytype = 0;
51
    /** @var string|null */
52
    public $_php_class = null;
53
54 1
    /**
55
     * Build an xml-rpc value.
56 1
     *
57 1
     * When no value or type is passed in, the value is left uninitialized, and the value can be added later.
58
     *
59 1
     * @param Value[]|mixed $val if passing in an array, all array elements should be PhpXmlRpc\Value themselves
60
     * @param string $type any valid xml-rpc type name (lowercase): i4, int, boolean, string, double, dateTime.iso8601,
61
     *                     base64, array, struct, null.
62
     *                     If null, 'string' is assumed.
63
     *                     You should refer to http://xmlrpc.com/spec.md for more information on what each of these mean.
64
     */
65
    public function __construct($val = -1, $type = '')
66
    {
67 594
        // optimization creep - do not call addXX, do it all inline.
68
        // downside: booleans will not be coerced anymore
69 594
        if ($val !== -1 || $type != '') {
70 435
            switch ($type) {
71
                case '':
72 594
                    $this->mytype = 1;
73
                    $this->me['string'] = $val;
74
                    break;
75
                case 'i4':
76
                case 'i8':
77
                case 'int':
78
                case 'double':
79
                case 'string':
80
                case 'boolean':
81
                case 'dateTime.iso8601':
82
                case 'base64':
83
                case 'null':
84
                    $this->mytype = 1;
85
                    $this->me[$type] = $val;
86
                    break;
87
                case 'array':
88
                    $this->mytype = 2;
89
                    $this->me['array'] = $val;
90
                    break;
91 746
                case 'struct':
92
                    $this->mytype = 3;
93
                    $this->me['struct'] = $val;
94
                    break;
95 746
                default:
96 745
                    $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
97 745
            }
98 251
        }
99 251
    }
100 251
101 743
    /**
102 743
     * Add a single php value to an xml-rpc value.
103 743
     *
104 698
     * If the xml-rpc value is an array, the php value is added as its last element.
105 677
     * If the xml-rpc value is empty (uninitialized), this method makes it a scalar value, and sets that value.
106 469
     * Fails if the xml-rpc value is not an array (i.e. a struct or a scalar) and already initialized.
107 448
     *
108 425
     * @param mixed $val
109 404
     * @param string $type allowed values: i4, i8, int, boolean, string, double, dateTime.iso8601, base64, null.
110 717
     * @return int 1 or 0 on failure
111 717
     */
112 717
    public function addScalar($val, $type = 'string')
113 403
    {
114 241
        $typeOf = null;
115 241
        if (isset(static::$xmlrpcTypes[$type])) {
116 241
            $typeOf = static::$xmlrpcTypes[$type];
117 295
        }
118 295
119 295
        if ($typeOf !== 1) {
120 295
            $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)");
121
            return 0;
122
        }
123
124
        // coerce booleans into correct values
125 746
        /// @todo we should either do it for datetimes, integers, i8 and doubles, too,
126
        ///       or just plain remove this check, implemented on booleans only...
127
        if ($type == static::$xmlrpcBoolean) {
128
            if (strcasecmp($val, 'true') == 0 || $val == 1 || ($val == true && strcasecmp($val, 'false'))) {
129
                $val = true;
130
            } else {
131
                $val = false;
132
            }
133
        }
134
135
        switch ($this->mytype) {
136
            case 1:
137
                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value');
138
                return 0;
139 1
            case 3:
140
                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value');
141 1
                return 0;
142 1
            case 2:
143 1
                // we're adding a scalar value to an array here
144
                $class = get_class($this);
145
                $this->me['array'][] = new $class($val, $type);
146 1
147
                return 1;
148
            default:
149
                // a scalar, so set the value and remember we're scalar
150
                $this->me[$type] = $val;
151
                $this->mytype = $typeOf;
152
153
                return 1;
154 1
        }
155
    }
156
157
    /**
158
     * Add an array of xml-rpc value objects to an xml-rpc value.
159
     *
160
     * If the xml-rpc value is an array, the elements are appended to the existing ones.
161
     * If the xml-rpc value is empty (uninitialized), this method makes it an array value, and sets that value.
162 1
     * Fails otherwise.
163 1
     *
164
     * @param Value[] $values
165
     * @return int 1 or 0 on failure
166 1
     *
167 1
     * @todo add some checking for $values to be an array of xml-rpc values?
168 1
     */
169
    public function addArray($values)
170
    {
171
        if ($this->mytype == 0) {
172
            $this->mytype = static::$xmlrpcTypes['array'];
173
            $this->me['array'] = $values;
174
175
            return 1;
176
        } elseif ($this->mytype == 2) {
177
            // we're adding to an array here
178
            $this->me['array'] = array_merge($this->me['array'], $values);
179
180
            return 1;
181
        } else {
182
            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
183
            return 0;
184
        }
185
    }
186
187
    /**
188
     * Merges an array of named xml-rpc value objects into an xml-rpc value.
189
     *
190
     * If the xml-rpc value is a struct, the elements are merged with the existing ones (overwriting existing ones).
191
     * If the xml-rpc value is empty (uninitialized), this method makes it a struct value, and sets that value.
192
     * Fails otherwise.
193
     *
194
     * @param Value[] $values
195
     * @return int 1 or 0 on failure
196 1
     *
197
     * @todo add some checking for $values to be an array?
198 1
     */
199
    public function addStruct($values)
200
    {
201
        if ($this->mytype == 0) {
202
            $this->mytype = static::$xmlrpcTypes['struct'];
203 1
            $this->me['struct'] = $values;
204
205 1
            return 1;
206
        } elseif ($this->mytype == 3) {
207 1
            // we're adding to a struct here
208
            $this->me['struct'] = array_merge($this->me['struct'], $values);
209
210
            return 1;
211
        } else {
212
            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
213
            return 0;
214
        }
215
    }
216
217
    /**
218
     * Returns a string containing either "struct", "array", "scalar" or "undef", describing the base type of the value.
219
     *
220
     * @return string
221
     */
222
    public function kindOf()
223
    {
224
        switch ($this->mytype) {
225
            case 3:
226
                return 'struct';
227 1
            case 2:
228
                return 'array';
229 1
            case 1:
230
                return 'scalar';
231
            default:
232
                return 'undef';
233
        }
234 1
    }
235
236 1
    /**
237
     * @param string $typ
238 1
     * @param Value[]|mixed $val
239
     * @param string $charsetEncoding
240
     * @return string
241
     */
242
    protected function serializedata($typ, $val, $charsetEncoding = '')
243
    {
244
        $rs = '';
245
246
        if (!isset(static::$xmlrpcTypes[$typ])) {
247
            return $rs;
248
        }
249
250 627
        switch (static::$xmlrpcTypes[$typ]) {
251
            case 1:
252 627
                switch ($typ) {
253 627
                    case static::$xmlrpcBase64:
254 150
                        $rs .= "<{$typ}>" . base64_encode($val) . "</{$typ}>";
0 ignored issues
show
Bug introduced by
It seems like $val can also be of type PhpXmlRpc\Value[]; however, parameter $string of base64_encode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

254
                        $rs .= "<{$typ}>" . base64_encode(/** @scrutinizer ignore-type */ $val) . "</{$typ}>";
Loading history...
255 606
                        break;
256 214
                    case static::$xmlrpcBoolean:
257 583
                        $rs .= "<{$typ}>" . ($val ? '1' : '0') . "</{$typ}>";
258 583
                        break;
259
                    case static::$xmlrpcString:
260
                        // Do NOT use htmlentities, since it will produce named html entities, which are invalid xml
261
                        $rs .= "<{$typ}>" . $this->getCharsetEncoder()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</{$typ}>";
262
                        break;
263
                    case static::$xmlrpcInt:
264
                    case static::$xmlrpcI4:
265
                    case static::$xmlrpcI8:
266
                        $rs .= "<{$typ}>" . (int)$val . "</{$typ}>";
267
                        break;
268
                    case static::$xmlrpcDouble:
269
                        // avoid using standard conversion of float to string because it is locale-dependent,
270 705
                        // and also because the xml-rpc spec forbids exponential notation.
271
                        // sprintf('%F') could be most likely ok, but it fails e.g. on 2e-14.
272 705
                        // The code below tries its best at keeping max precision while avoiding exp notation,
273
                        // but there is of course no limit in the number of decimal places to be used...
274 705
                        $rs .= "<{$typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, PhpXmlRpc::$xmlpc_double_precision, '.', '')) . "</{$typ}>";
275 2
                        break;
276
                    case static::$xmlrpcDateTime:
277
                        if (is_string($val)) {
278 705
                            $rs .= "<{$typ}>{$val}</{$typ}>";
279 705
                        // DateTimeInterface is not present in php 5.4...
280
                        } elseif (is_a($val, 'DateTimeInterface') || is_a($val, 'DateTime')) {
281 684
                            $rs .= "<{$typ}>" . $val->format('Ymd\TH:i:s') . "</{$typ}>";
282 23
                        } elseif (is_int($val)) {
283 23
                            $rs .= "<{$typ}>" . date('Ymd\TH:i:s', $val) . "</{$typ}>";
284 684
                        } else {
285 46
                            // not really a good idea here: but what should we output anyway? left for backward compat...
286 46
                            $rs .= "<{$typ}>{$val}</{$typ}>";
287 663
                        }
288
                        break;
289 573
                    case static::$xmlrpcNull:
290 573
                        if (PhpXmlRpc::$xmlrpc_null_apache_encoding) {
291 474
                            $rs .= "<ex:nil/>";
292 68
                        } else {
293 68
                            $rs .= "<nil/>";
294 430
                        }
295 430
                        break;
296 68
                    default:
297
                        // no standard type value should arrive here, but provide a possibility
298
                        // for xml-rpc values of unknown type...
299
                        $rs .= "<{$typ}>{$val}</{$typ}>";
300
                }
301
                break;
302 24
            case 3:
303 24
                // struct
304 47
                if ($this->_php_class) {
305 25
                    $rs .= '<struct php_class="' . $this->_php_class . "\">\n";
306 24
                } else {
307 23
                    $rs .= "<struct>\n";
308 22
                }
309 23
                $charsetEncoder = $this->getCharsetEncoder();
310 23
                /** @var Value $val2 */
311
                foreach ($val as $key2 => $val2) {
312
                    $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n";
313
                    //$rs.=$this->serializeval($val2);
314
                    $rs .= $val2->serialize($charsetEncoding);
315 25
                    $rs .= "</member>\n";
316 23
                }
317 23
                $rs .= '</struct>';
318 23
                break;
319
            case 2:
320 1
                // array
321
                $rs .= "<array>\n<data>\n";
322 23
                /** @var Value $element */
323
                foreach ($val as $element) {
324
                    //$rs.=$this->serializeval($val[$i]);
325
                    $rs .= $element->serialize($charsetEncoding);
326
                }
327
                $rs .= "</data>\n</array>";
328 684
                break;
329 218
            default:
330
                break;
331 133
        }
332 1
333
        return $rs;
334 133
    }
335
336 133
    /**
337
     * Returns the xml representation of the value. XML prologue not included.
338 133
     *
339 112
     * @param string $charsetEncoding the charset to be used for serialization. If null, US-ASCII is assumed
340
     * @return string
341 112
     */
342 112
    public function serialize($charsetEncoding = '')
343
    {
344 133
        $val = reset($this->me);
345 133
        $typ = key($this->me);
346 154
347
        return '<value>' . $this->serializedata($typ, $val, $charsetEncoding) . "</value>\n";
348 154
    }
349
350 154
    /**
351
     * Checks whether a struct member with a given name is present.
352 154
     *
353
     * Works only on xml-rpc values of type struct.
354 154
     *
355 154
     * @param string $key the name of the struct member to be looked up
356
     * @return boolean
357
     *
358
     * @deprecated use array access, e.g. isset($val[$key])
359
     */
360 705
    public function structmemexists($key)
361
    {
362
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
363
364
        return array_key_exists($key, $this->me['struct']);
365
    }
366
367
    /**
368
     * Returns the value of a given struct member (an xml-rpc value object in itself).
369
     * Will raise a php warning if struct member of given name does not exist.
370 705
     *
371
     * @param string $key the name of the struct member to be looked up
372 705
     * @return Value
373 705
     *
374
     * @deprecated use array access, e.g. $val[$key]
375 705
     */
376
    public function structmem($key)
377
    {
378
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
379
380
        return $this->me['struct'][$key];
381
    }
382
383
    /**
384
     * Reset internal pointer for xml-rpc values of type struct.
385
     * @return void
386
     *
387
     * @deprecated iterate directly over the object using foreach instead
388
     */
389 2
    public function structreset()
390
    {
391
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
392
393 2
        reset($this->me['struct']);
394
    }
395
396
    /**
397
     * Return next member element for xml-rpc values of type struct.
398
     *
399
     * @return Value
400
     * @throws \Error starting with php 8.0, this function should not be used, as it will always throw
401
     *
402
     * @deprecated iterate directly over the object using foreach instead
403
     */
404
    public function structeach()
405
    {
406 29
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
407
408
        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...
409
    }
410 29
411
    /**
412
     * Returns the value of a scalar xml-rpc value (base 64 decoding is automatically handled here)
413
     *
414
     * @return mixed
415
     */
416
    public function scalarval()
417
    {
418
        $b = reset($this->me);
419
420
        return $b;
421
    }
422
423
    /**
424
     * Returns the type of the xml-rpc value.
425
     *
426
     * For integers, 'int' is always returned in place of 'i4'. 'i8' is considered a separate type and returned as such
427
     *
428
     * @return string
429
     */
430
    public function scalartyp()
431
    {
432
        reset($this->me);
433
        $a = key($this->me);
434
        if ($a == static::$xmlrpcI4) {
435
            $a = static::$xmlrpcInt;
436
        }
437
438
        return $a;
439
    }
440
441
    /**
442
     * Returns the m-th member of an xml-rpc value of array type.
443
     *
444 660
     * @param integer $key the index of the value to be retrieved (zero based)
445
     *
446 660
     * @return Value
447
     *
448 660
     * @deprecated use array access, e.g. $val[$key]
449
     */
450
    public function arraymem($key)
451
    {
452
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
453
454
        return $this->me['array'][$key];
455
    }
456
457
    /**
458 454
     * Returns the number of members in an xml-rpc value of array type.
459
     *
460 454
     * @return integer
461 454
     *
462 454
     * @deprecated use count() instead
463
     */
464
    public function arraysize()
465
    {
466 454
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
467
468
        return count($this->me['array']);
469
    }
470
471
    /**
472
     * Returns the number of members in an xml-rpc value of struct type.
473
     *
474
     * @return integer
475
     *
476
     * @deprecated use count() instead
477
     */
478 43
    public function structsize()
479
    {
480
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
481
482 43
        return count($this->me['struct']);
483
    }
484
485
    /**
486
     * Returns the number of members in an xml-rpc value:
487
     * - 0 for uninitialized values
488
     * - 1 for scalar values
489
     * - the number of elements for struct and array values
490
     *
491
     * @return integer
492 44
     */
493
    #[\ReturnTypeWillChange]
494
    public function count()
495
    {
496 44
        switch ($this->mytype) {
497
            case 3:
498
                return count($this->me['struct']);
499
            case 2:
500
                return count($this->me['array']);
501
            case 1:
502
                return 1;
503
            default:
504
                return 0;
505
        }
506 23
    }
507
508
    /**
509
     * Implements the IteratorAggregate interface
510 23
     * @internal required to be public to implement an Interface
511
     *
512
     * @return \ArrayIterator
513
     */
514
    #[\ReturnTypeWillChange]
515
    public function getIterator()
516
    {
517
        switch ($this->mytype) {
518
            case 3:
519
                return new \ArrayIterator($this->me['struct']);
520
            case 2:
521
                return new \ArrayIterator($this->me['array']);
522 25
            case 1:
523
                return new \ArrayIterator($this->me);
524 25
            default:
525 25
                return new \ArrayIterator();
526
        }
527 25
    }
528 25
529
    /**
530
     * @internal required to be public to implement an Interface
531
     *
532
     * @param mixed $offset
533
     * @param mixed $value
534
     * @return void
535
     *
536
     * @throws \Exception
537
     */
538
    #[\ReturnTypeWillChange]
539
    public function offsetSet($offset, $value)
540
    {
541
        switch ($this->mytype) {
542
            case 3:
543 258
                if (!($value instanceof \PhpXmlRpc\Value)) {
544
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Struct');
545 258
                }
546 258
                if (is_null($offset)) {
547 65
                    // disallow struct members with empty names
548 215
                    throw new \Exception('It is not possible to add anonymous members to an XML-RPC Struct');
549 215
                } else {
550
                    $this->me['struct'][$offset] = $value;
551
                }
552
                return;
553
            case 2:
554
                if (!($value instanceof \PhpXmlRpc\Value)) {
555
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Array');
556
                }
557
                if (is_null($offset)) {
558
                    $this->me['array'][] = $value;
559
                } else {
560
                    // nb: we are not checking that $offset is above the existing array range...
561
                    $this->me['array'][$offset] = $value;
562
                }
563
                return;
564 22
            case 1:
565
// todo: handle i4 vs int
566 22
                reset($this->me);
567 22
                $type = key($this->me);
568
                if ($type != $offset) {
569
                    throw new \Exception('');
570
                }
571
                $this->me[$type] = $value;
572
                return;
573
            default:
574
                // it would be nice to allow empty values to be turned into non-empty ones this way, but we miss info to do so
575
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be set using array index");
576
        }
577
    }
578 22
579 22
    /**
580
     * @internal required to be public to implement an Interface
581
     *
582 22
     * @param mixed $offset
583 22
     * @return bool
584
     */
585
    #[\ReturnTypeWillChange]
586
    public function offsetExists($offset)
587
    {
588 22
        switch ($this->mytype) {
589
            case 3:
590
                return isset($this->me['struct'][$offset]);
591
            case 2:
592
                return isset($this->me['array'][$offset]);
593
            case 1:
594
// todo: handle i4 vs int
595
                return $offset == $this->scalartyp();
596
            default:
597
                return false;
598
        }
599
    }
600
601
    /**
602
     * @internal required to be public to implement an Interface
603
     *
604
     * @param mixed $offset
605
     * @return void
606
     *
607
     * @throws \Exception
608
     */
609
    #[\ReturnTypeWillChange]
610
    public function offsetUnset($offset)
611
    {
612
        switch ($this->mytype) {
613
            case 3:
614
                unset($this->me['struct'][$offset]);
615
                return;
616
            case 2:
617
                unset($this->me['array'][$offset]);
618
                return;
619
            case 1:
620
                // can not remove value from a scalar
621
                throw new \Exception("XML-RPC Value is of type 'scalar' and its value can not be unset using array index");
622
            default:
623
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be unset using array index");
624
        }
625
    }
626
627
    /**
628
     * @internal required to be public to implement an Interface
629
     *
630
     * @param mixed $offset
631
     * @return mixed|Value|null
632
     * @throws \Exception
633
     */
634
    #[\ReturnTypeWillChange]
635
    public function offsetGet($offset)
636
    {
637
        switch ($this->mytype) {
638
            case 3:
639
                return isset($this->me['struct'][$offset]) ? $this->me['struct'][$offset] : null;
640
            case 2:
641
                return isset($this->me['array'][$offset]) ? $this->me['array'][$offset] : null;
642
            case 1:
643
// on bad type: null or exception?
644
                $value = reset($this->me);
645
                $type = key($this->me);
646
                return $type == $offset ? $value : null;
647
            default:
648
// return null or exception?
649
                throw new \Exception("XML-RPC Value is of type 'undef' and can not be accessed using array index");
650
        }
651
    }
652
}
653