Completed
Push — master ( 3167db...70e3fd )
by Gaetano
07:00
created

Value::arraysize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 6
ccs 1
cts 1
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Charset;
6
7
/**
8
 * This class enables the creation of values for XML-RPC, by encapsulating plain php values.
9
 */
10
class Value implements \Countable, \IteratorAggregate, \ArrayAccess
11
{
12
    public static $xmlrpcI4 = "i4";
13
    public static $xmlrpcI8 = "i8";
14
    public static $xmlrpcInt = "int";
15
    public static $xmlrpcBoolean = "boolean";
16
    public static $xmlrpcDouble = "double";
17
    public static $xmlrpcString = "string";
18
    public static $xmlrpcDateTime = "dateTime.iso8601";
19
    public static $xmlrpcBase64 = "base64";
20
    public static $xmlrpcArray = "array";
21
    public static $xmlrpcStruct = "struct";
22
    public static $xmlrpcValue = "undefined";
23
    public static $xmlrpcNull = "null";
24
25
    public static $xmlrpcTypes = array(
26
        "i4" => 1,
27
        "i8" => 1,
28
        "int" => 1,
29
        "boolean" => 1,
30
        "double" => 1,
31
        "string" => 1,
32
        "dateTime.iso8601" => 1,
33
        "base64" => 1,
34
        "array" => 2,
35
        "struct" => 3,
36
        "null" => 1,
37
    );
38
39
    /// @todo: do these need to be public?
40
    public $me = array();
41
    public $mytype = 0;
42
    public $_php_class = null;
43
44
    /**
45
     * Build an xmlrpc value.
46
     *
47
     * When no value or type is passed in, the value is left uninitialized, and the value can be added later.
48
     *
49
     * @param mixed $val if passing in an array, all array elements should be PhpXmlRpc\Value themselves
50
     * @param string $type any valid xmlrpc type name (lowercase): i4, int, boolean, string, double, dateTime.iso8601,
51
     *                     base64, array, struct, null.
52
     *                     If null, 'string' is assumed.
53
     *                     You should refer to http://www.xmlrpc.com/spec for more information on what each of these mean.
54
     */
55 630
    public function __construct($val = -1, $type = '')
56
    {
57
        // optimization creep - do not call addXX, do it all inline.
58
        // downside: booleans will not be coerced anymore
59 630
        if ($val !== -1 || $type != '') {
60 629
            switch ($type) {
61 629
                case '':
62 280
                    $this->mytype = 1;
63 280
                    $this->me['string'] = $val;
64 280
                    break;
65 629
                case 'i4':
66 629
                case 'i8':
67 629
                case 'int':
68 589
                case 'double':
69 570
                case 'string':
70 382
                case 'boolean':
71 382
                case 'dateTime.iso8601':
72 363
                case 'base64':
73 344
                case 'null':
74 606
                    $this->mytype = 1;
75 606
                    $this->me[$type] = $val;
76 606
                    break;
77 343
                case 'array':
78 215
                    $this->mytype = 2;
79 215
                    $this->me['array'] = $val;
80 215
                    break;
81 244
                case 'struct':
82 244
                    $this->mytype = 3;
83 244
                    $this->me['struct'] = $val;
84 244
                    break;
85
                default:
86
                    Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
87
            }
88
        }
89 630
    }
90
91
    /**
92
     * Add a single php value to an xmlrpc value.
93
     *
94
     * If the xmlrpc value is an array, the php value is added as its last element.
95
     * If the xmlrpc value is empty (uninitialized), this method makes it a scalar value, and sets that value.
96
     * Fails if the xmlrpc value is not an array and already initialized.
97
     *
98
     * @param mixed $val
99
     * @param string $type allowed values: i4, i8, int, boolean, string, double, dateTime.iso8601, base64, null.
100
     *
101
     * @return int 1 or 0 on failure
102
     */
103 1
    public function addScalar($val, $type = 'string')
104
    {
105 1
        $typeOf = null;
106 1
        if (isset(static::$xmlrpcTypes[$type])) {
107 1
            $typeOf = static::$xmlrpcTypes[$type];
108
        }
109
110 1
        if ($typeOf !== 1) {
111
            Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)");
112
            return 0;
113
        }
114
115
        // coerce booleans into correct values
116
        // NB: we should either do it for datetimes, integers, i8 and doubles, too,
117
        // or just plain remove this check, implemented on booleans only...
118 1
        if ($type == static::$xmlrpcBoolean) {
119
            if (strcasecmp($val, 'true') == 0 || $val == 1 || ($val == true && strcasecmp($val, 'false'))) {
120
                $val = true;
121
            } else {
122
                $val = false;
123
            }
124
        }
125
126 1
        switch ($this->mytype) {
127 1
            case 1:
128
                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value');
129
                return 0;
130 1
            case 3:
131 1
                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value');
132
                return 0;
133
            case 2:
134
                // we're adding a scalar value to an array here
135
                $this->me['array'][] = new Value($val, $type);
136
137
                return 1;
138
            default:
139
                // a scalar, so set the value and remember we're scalar
140
                $this->me[$type] = $val;
141
                $this->mytype = $typeOf;
142
143
                return 1;
144
        }
145
    }
146
147
    /**
148
     * Add an array of xmlrpc value objects to an xmlrpc value.
149
     *
150
     * If the xmlrpc value is an array, the elements are appended to the existing ones.
151
     * If the xmlrpc value is empty (uninitialized), this method makes it an array value, and sets that value.
152
     * Fails otherwise.
153
     *
154
     * @param Value[] $values
155
     *
156
     * @return int 1 or 0 on failure
157
     *
158
     * @todo add some checking for $values to be an array of xmlrpc values?
159
     */
160 1 View Code Duplication
    public function addArray($values)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
161
    {
162 1
        if ($this->mytype == 0) {
163
            $this->mytype = static::$xmlrpcTypes['array'];
164
            $this->me['array'] = $values;
165
166
            return 1;
167 1
        } elseif ($this->mytype == 2) {
168
            // we're adding to an array here
169 1
            $this->me['array'] = array_merge($this->me['array'], $values);
170
171 1
            return 1;
172
        } else {
173
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
174
            return 0;
175
        }
176
    }
177
178
    /**
179
     * Merges an array of named xmlrpc value objects into an xmlrpc value.
180
     *
181
     * If the xmlrpc value is a struct, the elements are merged with the existing ones (overwriting existing ones).
182
     * If the xmlrpc value is empty (uninitialized), this method makes it a struct value, and sets that value.
183
     * Fails otherwise.
184
     *
185
     * @param Value[] $values
186
     *
187
     * @return int 1 or 0 on failure
188
     *
189
     * @todo add some checking for $values to be an array?
190
     */
191 1 View Code Duplication
    public function addStruct($values)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
192
    {
193 1
        if ($this->mytype == 0) {
194
            $this->mytype = static::$xmlrpcTypes['struct'];
195
            $this->me['struct'] = $values;
196
197
            return 1;
198 1
        } elseif ($this->mytype == 3) {
199
            // we're adding to a struct here
200 1
            $this->me['struct'] = array_merge($this->me['struct'], $values);
201
202 1
            return 1;
203
        } else {
204
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
205
            return 0;
206
        }
207
    }
208
209
    /**
210
     * Returns a string containing either "struct", "array", "scalar" or "undef", describing the base type of the value.
211
     *
212
     * @return string
213
     */
214 545
    public function kindOf()
215
    {
216 545
        switch ($this->mytype) {
217 545
            case 3:
218 134
                return 'struct';
219
                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...
220 526
            case 2:
221 192
                return 'array';
222
                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...
223 506
            case 1:
224 506
                return 'scalar';
225
                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...
226
            default:
227
                return 'undef';
228
        }
229
    }
230
231 593
    protected function serializedata($typ, $val, $charsetEncoding = '')
232
    {
233 593
        $rs = '';
234
235 593
        if (!isset(static::$xmlrpcTypes[$typ])) {
236 2
            return $rs;
237
        }
238
239 593
        switch (static::$xmlrpcTypes[$typ]) {
240 593
            case 1:
241
                switch ($typ) {
242 574
                    case static::$xmlrpcBase64:
243 20
                        $rs .= "<${typ}>" . base64_encode($val) . "</${typ}>";
244 20
                        break;
245 574
                    case static::$xmlrpcBoolean:
246 21
                        $rs .= "<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
247 21
                        break;
248 555
                    case static::$xmlrpcString:
249
                        // Do NOT use htmlentities, since it will produce named html entities, which are invalid xml
250 476
                        $rs .= "<${typ}>" . Charset::instance()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</${typ}>";
251 476
                        break;
252 404
                    case static::$xmlrpcInt:
253 60
                    case static::$xmlrpcI4:
254 60
                    case static::$xmlrpcI8:
255 365
                        $rs .= "<${typ}>" . (int)$val . "</${typ}>";
256 365
                        break;
257 60
                    case static::$xmlrpcDouble:
258
                        // avoid using standard conversion of float to string because it is locale-dependent,
259
                        // and also because the xmlrpc spec forbids exponential notation.
260
                        // sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
261
                        // The code below tries its best at keeping max precision while avoiding exp notation,
262
                        // but there is of course no limit in the number of decimal places to be used...
263 21
                        $rs .= "<${typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, 128, '.', '')) . "</${typ}>";
264 21
                        break;
265 41
                    case static::$xmlrpcDateTime:
266 21
                        if (is_string($val)) {
267 21
                            $rs .= "<${typ}>${val}</${typ}>";
268 20
                        } elseif (is_a($val, 'DateTime')) {
269 20
                            $rs .= "<${typ}>" . $val->format('Ymd\TH:i:s') . "</${typ}>";
270 20
                        } elseif (is_int($val)) {
271 20
                            $rs .= "<${typ}>" . strftime("%Y%m%dT%H:%M:%S", $val) . "</${typ}>";
272
                        } else {
273
                            // not really a good idea here: but what shall we output anyway? left for backward compat...
274
                            $rs .= "<${typ}>${val}</${typ}>";
275
                        }
276 21
                        break;
277 20
                    case static::$xmlrpcNull:
278 20
                        if (PhpXmlRpc::$xmlrpc_null_apache_encoding) {
279 20
                            $rs .= "<ex:nil/>";
280
                        } else {
281 1
                            $rs .= "<nil/>";
282
                        }
283 20
                        break;
284
                    default:
285
                        // no standard type value should arrive here, but provide a possibility
286
                        // for xmlrpc values of unknown type...
287
                        $rs .= "<${typ}>${val}</${typ}>";
288
                }
289 574
                break;
290 194
            case 3:
291
                // struct
292 117
                if ($this->_php_class) {
293
                    $rs .= '<struct php_class="' . $this->_php_class . "\">\n";
294
                } else {
295 117
                    $rs .= "<struct>\n";
296
                }
297 117
                $charsetEncoder = Charset::instance();
298
                /** @var Value $val2 */
299 117
                foreach ($val as $key2 => $val2) {
300 98
                    $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n";
301
                    //$rs.=$this->serializeval($val2);
302 98
                    $rs .= $val2->serialize($charsetEncoding);
303 98
                    $rs .= "</member>\n";
304
                }
305 117
                $rs .= '</struct>';
306 117
                break;
307 136
            case 2:
308
                // array
309 136
                $rs .= "<array>\n<data>\n";
310
                /** @var Value $element */
311 136
                foreach ($val as $element) {
312
                    //$rs.=$this->serializeval($val[$i]);
313 136
                    $rs .= $element->serialize($charsetEncoding);
314
                }
315 136
                $rs .= "</data>\n</array>";
316 136
                break;
317
            default:
318
                break;
319
        }
320
321 593
        return $rs;
322
    }
323
324
    /**
325
     * Returns the xml representation of the value. XML prologue not included.
326
     *
327
     * @param string $charsetEncoding the charset to be used for serialization. if null, US-ASCII is assumed
328
     *
329
     * @return string
330
     */
331
    public function serialize($charsetEncoding = '')
332
    {
333 593
        $val = reset($this->me);
334 593
        $typ = key($this->me);
335
336 593
        return '<value>' . $this->serializedata($typ, $val, $charsetEncoding) . "</value>\n";
337
    }
338
339
    /**
340
     * Checks whether a struct member with a given name is present.
341
     *
342
     * Works only on xmlrpc values of type struct.
343
     *
344
     * @param string $key the name of the struct member to be looked up
345
     *
346
     * @return boolean
347
     *
348
     * @deprecated use array access, e.g. isset($val[$key])
349
     */
350
    public function structmemexists($key)
351
    {
352
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
353
354 2
        return array_key_exists($key, $this->me['struct']);
355
    }
356
357
    /**
358
     * Returns the value of a given struct member (an xmlrpc value object in itself).
359
     * Will raise a php warning if struct member of given name does not exist.
360
     *
361
     * @param string $key the name of the struct member to be looked up
362
     *
363
     * @return Value
364
     *
365
     * @deprecated use array access, e.g. $val[$key]
366
     */
367
    public function structmem($key)
368
    {
369
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
370
371 27
        return $this->me['struct'][$key];
372
    }
373
374
    /**
375
     * Reset internal pointer for xmlrpc values of type struct.
376
     * @deprecated iterate directly over the object using foreach instead
377
     */
378
    public function structreset()
379
    {
380
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
381
382
        reset($this->me['struct']);
383
    }
384
385
    /**
386
     * Return next member element for xmlrpc values of type struct.
387
     *
388
     * @return Value
389
     *
390
     * @deprecated iterate directly over the object using foreach instead
391
     */
392
    public function structeach()
393
    {
394
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
395
396
        return @each($this->me['struct']);
397
    }
398
399
    /**
400
     * Returns the value of a scalar xmlrpc value (base 64 decoding is automatically handled here)
401
     *
402
     * @return mixed
403
     */
404
    public function scalarval()
405
    {
406 537
        $b = reset($this->me);
407
408 537
        return $b;
409
    }
410
411
    /**
412
     * Returns the type of the xmlrpc value.
413
     *
414
     * For integers, 'int' is always returned in place of 'i4'. 'i8' is considered a separate type and returned as such
415
     *
416
     * @return string
417
     */
418
    public function scalartyp()
419
    {
420 392
        reset($this->me);
421 392
        $a = key($this->me);
422 392
        if ($a == static::$xmlrpcI4) {
423
            $a = static::$xmlrpcInt;
424
        }
425
426 392
        return $a;
427
    }
428
429
    /**
430
     * Returns the m-th member of an xmlrpc value of array type.
431
     *
432
     * @param integer $key the index of the value to be retrieved (zero based)
433
     *
434
     * @return Value
435
     *
436
     * @deprecated use array access, e.g. $val[$key]
437
     */
438
    public function arraymem($key)
439
    {
440
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
441
442 39
        return $this->me['array'][$key];
443
    }
444
445
    /**
446
     * Returns the number of members in an xmlrpc value of array type.
447
     *
448
     * @return integer
449
     *
450
     * @deprecated use count() instead
451
     */
452
    public function arraysize()
453
    {
454
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
455
456 40
        return count($this->me['array']);
457
    }
458
459
    /**
460
     * Returns the number of members in an xmlrpc value of struct type.
461
     *
462
     * @return integer
463
     *
464
     * @deprecated use count() instead
465
     */
466
    public function structsize()
467
    {
468
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
469
470 20
        return count($this->me['struct']);
471
    }
472
473
    /**
474
     * Returns the number of members in an xmlrpc value:
475
     * - 0 for uninitialized values
476
     * - 1 for scalar values
477
     * - the number of elements for struct and array values
478
     *
479
     * @return integer
480
     */
481
    public function count()
482
    {
483 21
        switch ($this->mytype) {
484 21
            case 3:
485
                return count($this->me['struct']);
486 21
            case 2:
487 21
                return count($this->me['array']);
488
            case 1:
489
                return 1;
490
            default:
491
                return 0;
492
        }
493
    }
494
495
    /**
496
     * Implements the IteratorAggregate interface
497
     *
498
     * @return \ArrayIterator
499
     */
500
    public function getIterator() {
501 230
        switch ($this->mytype) {
502 230
            case 3:
503 57
                return new \ArrayIterator($this->me['struct']);
504 192
            case 2:
505 192
                return new \ArrayIterator($this->me['array']);
506
            case 1:
507
                return new \ArrayIterator($this->me);
508
            default:
509
                return new \ArrayIterator();
510
        }
511
    }
512
513
    public function offsetSet($offset, $value) {
514
515 20
        switch ($this->mytype) {
516 20
            case 3:
517
                if (!($value instanceof \PhpXmlRpc\Value)) {
518
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Struct');
519
                }
520
                if (is_null($offset)) {
521
                    // disallow struct members with empty names
522
                    throw new \Exception('It is not possible to add anonymous members to an XML-RPC Struct');
523
                } else {
524
                    $this->me['struct'][$offset] = $value;
525
                }
526
                return;
527 20
            case 2:
528 20
                if (!($value instanceof \PhpXmlRpc\Value)) {
529
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Array');
530
                }
531 20
                if (is_null($offset)) {
532 20
                    $this->me['array'][] = $value;
533
                } else {
534
                    // nb: we are not checking that $offset is above the existing array range...
535
                    $this->me['array'][$offset] = $value;
536
                }
537 20
                return;
538
            case 1:
539
// todo: handle i4 vs int
540
                reset($this->me);
541
                $type = key($this->me);
542
                if ($type != $offset) {
543
                    throw new \Exception('');
544
                }
545
                $this->me[$type] = $value;
546
                return;
547
            default:
548
                // 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
549
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be set using array index");
550
        }
551
    }
552
553
    public function offsetExists($offset) {
554
        switch ($this->mytype) {
555
            case 3:
556
                return isset($this->me['struct'][$offset]);
557
            case 2:
558
                return isset($this->me['array'][$offset]);
559
            case 1:
560
// todo: handle i4 vs int
561
                return $offset == $this->scalartyp();
562
            default:
563
                return false;
564
        }
565
    }
566
567
    public function offsetUnset($offset) {
568
        switch ($this->mytype) {
569
            case 3:
570
                unset($this->me['struct'][$offset]);
571
                return;
572
            case 2:
573
                unset($this->me['array'][$offset]);
574
                return;
575
            case 1:
576
                // can not remove value from a scalar
577
                throw new \Exception("XML-RPC Value is of type 'scalar' and its value can not be unset using array index");
578
            default:
579
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be unset using array index");
580
        }
581
    }
582
583
    public function offsetGet($offset) {
584 137
        switch ($this->mytype) {
585 137
            case 3:
586 137
                return isset($this->me['struct'][$offset]) ? $this->me['struct'][$offset] : null;
587 20
            case 2:
588 20
                return isset($this->me['array'][$offset]) ? $this->me['array'][$offset] : null;
589
            case 1:
590
// on bad type: null or exception?
591
                $value = reset($this->me);
592
                $type = key($this->me);
593
                return $type == $offset ? $value : null;
594
            default:
595
// return null or exception?
596
                throw new \Exception("XML-RPC Value is of type 'undef' and can not be accessed using array index");
597
        }
598
    }
599
}
600