Completed
Push — master ( 87b7a4...6ce28d )
by Gaetano
11:11 queued 06:38
created

Value::scalartyp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 2
eloc 5
c 2
b 1
f 0
nc 2
nop 0
dl 0
loc 9
rs 10
ccs 4
cts 5
cp 0.8
crap 2.032
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 630
    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 630
        if ($val !== -1 || $type != '') {
61 629
            switch ($type) {
62 629
                case '':
63 280
                    $this->mytype = 1;
64 280
                    $this->me['string'] = $val;
65 280
                    break;
66 629
                case 'i4':
67 629
                case 'i8':
68 629
                case 'int':
69 589
                case 'double':
70 570
                case 'string':
71 401
                case 'boolean':
72 382
                case 'dateTime.iso8601':
73 363
                case 'base64':
74 344
                case 'null':
75 606
                    $this->mytype = 1;
76 606
                    $this->me[$type] = $val;
77 606
                    break;
78 343
                case 'array':
79 215
                    $this->mytype = 2;
80 215
                    $this->me['array'] = $val;
81 215
                    break;
82 244
                case 'struct':
83 244
                    $this->mytype = 3;
84 244
                    $this->me['struct'] = $val;
85 244
                    break;
86
                default:
87
                    Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
88
            }
89
        }
90 630
    }
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 545
    public function kindOf()
216
    {
217 545
        switch ($this->mytype) {
218 545
            case 3:
219 134
                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 526
            case 2:
222 192
                return 'array';
223
                break;
224 506
            case 1:
225 506
                return 'scalar';
226
                break;
227
            default:
228
                return 'undef';
229
        }
230
    }
231
232 593
    protected function serializedata($typ, $val, $charsetEncoding = '')
233
    {
234 593
        $rs = '';
235
236 593
        if (!isset(static::$xmlrpcTypes[$typ])) {
237 2
            return $rs;
238
        }
239
240 593
        switch (static::$xmlrpcTypes[$typ]) {
241 593
            case 1:
242
                switch ($typ) {
243 574
                    case static::$xmlrpcBase64:
244 20
                        $rs .= "<${typ}>" . base64_encode($val) . "</${typ}>";
245 20
                        break;
246 574
                    case static::$xmlrpcBoolean:
247 40
                        $rs .= "<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
248 40
                        break;
249 555
                    case static::$xmlrpcString:
250
                        // Do NOT use htmlentities, since it will produce named html entities, which are invalid xml
251 476
                        $rs .= "<${typ}>" . Charset::instance()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</${typ}>";
252 476
                        break;
253 404
                    case static::$xmlrpcInt:
254 60
                    case static::$xmlrpcI4:
255 60
                    case static::$xmlrpcI8:
256 365
                        $rs .= "<${typ}>" . (int)$val . "</${typ}>";
257 365
                        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 574
                break;
291 194
            case 3:
292
                // struct
293 117
                if ($this->_php_class) {
294 1
                    $rs .= '<struct php_class="' . $this->_php_class . "\">\n";
295
                } else {
296 117
                    $rs .= "<struct>\n";
297
                }
298 117
                $charsetEncoder = Charset::instance();
299
                /** @var Value $val2 */
300 117
                foreach ($val as $key2 => $val2) {
301 98
                    $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n";
302
                    //$rs.=$this->serializeval($val2);
303 98
                    $rs .= $val2->serialize($charsetEncoding);
304 98
                    $rs .= "</member>\n";
305
                }
306 117
                $rs .= '</struct>';
307 117
                break;
308 136
            case 2:
309
                // array
310 136
                $rs .= "<array>\n<data>\n";
311
                /** @var Value $element */
312 136
                foreach ($val as $element) {
313
                    //$rs.=$this->serializeval($val[$i]);
314 136
                    $rs .= $element->serialize($charsetEncoding);
315
                }
316 136
                $rs .= "</data>\n</array>";
317 136
                break;
318
            default:
319
                break;
320
        }
321
322 593
        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
    public function serialize($charsetEncoding = '')
333
    {
334 593
        $val = reset($this->me);
335 593
        $typ = key($this->me);
336
337 593
        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
    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
    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
     *
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
    public function scalarval()
406
    {
407 556
        $b = reset($this->me);
408
409 556
        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
    public function scalartyp()
420
    {
421 392
        reset($this->me);
422 392
        $a = key($this->me);
423 392
        if ($a == static::$xmlrpcI4) {
424
            $a = static::$xmlrpcInt;
425
        }
426
427 392
        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
    public function arraymem($key)
440
    {
441
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
442
443 39
        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
    public function arraysize()
454
    {
455
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
456
457 40
        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
    public function structsize()
468
    {
469
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
470
471 21
        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
    public function count()
483
    {
484 21
        switch ($this->mytype) {
485 21
            case 3:
486
                return count($this->me['struct']);
487 21
            case 2:
488 21
                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
    public function getIterator() {
502 230
        switch ($this->mytype) {
503 230
            case 3:
504 58
                return new \ArrayIterator($this->me['struct']);
505 192
            case 2:
506 192
                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
    public function offsetSet($offset, $value) {
515
516 20
        switch ($this->mytype) {
517 20
            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 20
            case 2:
529 20
                if (!($value instanceof \PhpXmlRpc\Value)) {
530
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Array');
531
                }
532 20
                if (is_null($offset)) {
533 20
                    $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 20
                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
    public function offsetGet($offset) {
585 137
        switch ($this->mytype) {
586 137
            case 3:
587 137
                return isset($this->me['struct'][$offset]) ? $this->me['struct'][$offset] : null;
588 20
            case 2:
589 20
                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