Passed
Push — master ( 593257...e1cd2d )
by Gaetano
03:24
created

Value::structreset()   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
    public $me = array();
42
    public $mytype = 0;
43
    public $_php_class = null;
44
45
    /**
46
     * Build an xmlrpc value.
47
     *
48
     * When no value or type is passed in, the value is left uninitialized, and the value can be added later.
49
     *
50
     * @param mixed $val if passing in an array, all array elements should be PhpXmlRpc\Value themselves
51
     * @param string $type any valid xmlrpc type name (lowercase): i4, int, boolean, string, double, dateTime.iso8601,
52
     *                     base64, array, struct, null.
53
     *                     If null, 'string' is assumed.
54
     *                     You should refer to http://www.xmlrpc.com/spec for more information on what each of these mean.
55
     */
56 537
    public function __construct($val = -1, $type = '')
57
    {
58
        // optimization creep - do not call addXX, do it all inline.
59
        // downside: booleans will not be coerced anymore
60 537
        if ($val !== -1 || $type != '') {
61 536
            switch ($type) {
62 536
                case '':
63 238
                    $this->mytype = 1;
64 238
                    $this->me['string'] = $val;
65 238
                    break;
66 536
                case 'i4':
67 536
                case 'i8':
68 536
                case 'int':
69 502
                case 'double':
70 486
                case 'string':
71 341
                case 'boolean':
72 325
                case 'dateTime.iso8601':
73 309
                case 'base64':
74 293
                case 'null':
75 516
                    $this->mytype = 1;
76 516
                    $this->me[$type] = $val;
77 516
                    break;
78 292
                case 'array':
79 182
                    $this->mytype = 2;
80 182
                    $this->me['array'] = $val;
81 182
                    break;
82 208
                case 'struct':
83 208
                    $this->mytype = 3;
84 208
                    $this->me['struct'] = $val;
85 208
                    break;
86
                default:
87
                    Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
88
            }
89
        }
90 537
    }
91
92
    /**
93
     * Add a single php value to an xmlrpc value.
94
     *
95
     * If the xmlrpc value is an array, the php value is added as its last element.
96
     * If the xmlrpc value is empty (uninitialized), this method makes it a scalar value, and sets that value.
97
     * Fails if the xmlrpc value is not an array and already initialized.
98
     *
99
     * @param mixed $val
100
     * @param string $type allowed values: i4, i8, int, boolean, string, double, dateTime.iso8601, base64, null.
101
     *
102
     * @return int 1 or 0 on failure
103
     */
104 1
    public function addScalar($val, $type = 'string')
105
    {
106 1
        $typeOf = null;
107 1
        if (isset(static::$xmlrpcTypes[$type])) {
108 1
            $typeOf = static::$xmlrpcTypes[$type];
109
        }
110
111 1
        if ($typeOf !== 1) {
112
            Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)");
113
            return 0;
114
        }
115
116
        // coerce booleans into correct values
117
        // NB: we should either do it for datetimes, integers, i8 and doubles, too,
118
        // or just plain remove this check, implemented on booleans only...
119 1
        if ($type == static::$xmlrpcBoolean) {
120
            if (strcasecmp($val, 'true') == 0 || $val == 1 || ($val == true && strcasecmp($val, 'false'))) {
121
                $val = true;
122
            } else {
123
                $val = false;
124
            }
125
        }
126
127 1
        switch ($this->mytype) {
128 1
            case 1:
129
                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value');
130
                return 0;
131 1
            case 3:
132 1
                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value');
133 1
                return 0;
134
            case 2:
135
                // we're adding a scalar value to an array here
136
                $this->me['array'][] = new Value($val, $type);
137
138
                return 1;
139
            default:
140
                // a scalar, so set the value and remember we're scalar
141
                $this->me[$type] = $val;
142
                $this->mytype = $typeOf;
143
144
                return 1;
145
        }
146
    }
147
148
    /**
149
     * Add an array of xmlrpc value objects to an xmlrpc value.
150
     *
151
     * If the xmlrpc value is an array, the elements are appended to the existing ones.
152
     * If the xmlrpc value is empty (uninitialized), this method makes it an array value, and sets that value.
153
     * Fails otherwise.
154
     *
155
     * @param Value[] $values
156
     *
157
     * @return int 1 or 0 on failure
158
     *
159
     * @todo add some checking for $values to be an array of xmlrpc values?
160
     */
161 1
    public function addArray($values)
162
    {
163 1
        if ($this->mytype == 0) {
164
            $this->mytype = static::$xmlrpcTypes['array'];
165
            $this->me['array'] = $values;
166
167
            return 1;
168 1
        } elseif ($this->mytype == 2) {
169
            // we're adding to an array here
170 1
            $this->me['array'] = array_merge($this->me['array'], $values);
171
172 1
            return 1;
173
        } else {
174
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
175
            return 0;
176
        }
177
    }
178
179
    /**
180
     * Merges an array of named xmlrpc value objects into an xmlrpc value.
181
     *
182
     * If the xmlrpc value is a struct, the elements are merged with the existing ones (overwriting existing ones).
183
     * If the xmlrpc value is empty (uninitialized), this method makes it a struct value, and sets that value.
184
     * Fails otherwise.
185
     *
186
     * @param Value[] $values
187
     *
188
     * @return int 1 or 0 on failure
189
     *
190
     * @todo add some checking for $values to be an array?
191
     */
192 1
    public function addStruct($values)
193
    {
194 1
        if ($this->mytype == 0) {
195
            $this->mytype = static::$xmlrpcTypes['struct'];
196
            $this->me['struct'] = $values;
197
198
            return 1;
199 1
        } elseif ($this->mytype == 3) {
200
            // we're adding to a struct here
201 1
            $this->me['struct'] = array_merge($this->me['struct'], $values);
202
203 1
            return 1;
204
        } else {
205
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
206
            return 0;
207
        }
208
    }
209
210
    /**
211
     * Returns a string containing either "struct", "array", "scalar" or "undef", describing the base type of the value.
212
     *
213
     * @return string
214
     */
215 461
    public function kindOf()
216
    {
217 461
        switch ($this->mytype) {
218 461
            case 3:
219 113
                return 'struct';
220
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
221 445
            case 2:
222 162
                return 'array';
223
                break;
224 428
            case 1:
225 428
                return 'scalar';
226
                break;
227
            default:
228
                return 'undef';
229
        }
230
    }
231
232 503
    protected function serializedata($typ, $val, $charsetEncoding = '')
233
    {
234 503
        $rs = '';
235
236 503
        if (!isset(static::$xmlrpcTypes[$typ])) {
237 2
            return $rs;
238
        }
239
240 503
        switch (static::$xmlrpcTypes[$typ]) {
241 503
            case 1:
242
                switch ($typ) {
243 487
                    case static::$xmlrpcBase64:
244 17
                        $rs .= "<${typ}>" . base64_encode($val) . "</${typ}>";
245 17
                        break;
246 487
                    case static::$xmlrpcBoolean:
247 34
                        $rs .= "<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
248 34
                        break;
249 471
                    case static::$xmlrpcString:
250
                        // Do NOT use htmlentities, since it will produce named html entities, which are invalid xml
251 404
                        $rs .= "<${typ}>" . Charset::instance()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</${typ}>";
252 404
                        break;
253 341
                    case static::$xmlrpcInt:
254 51
                    case static::$xmlrpcI4:
255 51
                    case static::$xmlrpcI8:
256 308
                        $rs .= "<${typ}>" . (int)$val . "</${typ}>";
257 308
                        break;
258 51
                    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 18
                        $rs .= "<${typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, 128, '.', '')) . "</${typ}>";
265 18
                        break;
266 35
                    case static::$xmlrpcDateTime:
267 18
                        if (is_string($val)) {
268 18
                            $rs .= "<${typ}>${val}</${typ}>";
269 17
                        } elseif (is_a($val, 'DateTime')) {
270 17
                            $rs .= "<${typ}>" . $val->format('Ymd\TH:i:s') . "</${typ}>";
271 17
                        } elseif (is_int($val)) {
272 17
                            $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 18
                        break;
278 18
                    case static::$xmlrpcNull:
279 18
                        if (PhpXmlRpc::$xmlrpc_null_apache_encoding) {
280 18
                            $rs .= "<ex:nil/>";
281
                        } else {
282 1
                            $rs .= "<nil/>";
283
                        }
284 18
                        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 487
                break;
291 164
            case 3:
292
                // struct
293 99
                if ($this->_php_class) {
294 1
                    $rs .= '<struct php_class="' . $this->_php_class . "\">\n";
295
                } else {
296 99
                    $rs .= "<struct>\n";
297
                }
298 99
                $charsetEncoder = Charset::instance();
299
                /** @var Value $val2 */
300 99
                foreach ($val as $key2 => $val2) {
301 83
                    $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n";
302
                    //$rs.=$this->serializeval($val2);
303 83
                    $rs .= $val2->serialize($charsetEncoding);
304 83
                    $rs .= "</member>\n";
305
                }
306 99
                $rs .= '</struct>';
307 99
                break;
308 115
            case 2:
309
                // array
310 115
                $rs .= "<array>\n<data>\n";
311
                /** @var Value $element */
312 115
                foreach ($val as $element) {
313
                    //$rs.=$this->serializeval($val[$i]);
314 115
                    $rs .= $element->serialize($charsetEncoding);
315
                }
316 115
                $rs .= "</data>\n</array>";
317 115
                break;
318
            default:
319
                break;
320
        }
321
322 503
        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 503
    public function serialize($charsetEncoding = '')
333
    {
334 503
        $val = reset($this->me);
335 503
        $typ = key($this->me);
336
337 503
        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 24
    public function structmem($key)
369
    {
370
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
371
372 24
        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
     *
391
     * @deprecated iterate directly over the object using foreach instead
392
     */
393
    public function structeach()
394
    {
395
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
396
397
        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

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