Passed
Push — master ( b66816...c27693 )
by Gaetano
04:58
created

Encoder::decodeXml()   D

Complexity

Conditions 18
Paths 150

Size

Total Lines 83
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 50.2413

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 18
eloc 45
c 1
b 0
f 0
nc 150
nop 2
dl 0
loc 83
ccs 22
cts 41
cp 0.5366
crap 50.2413
rs 4.45

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Logger;
6
use PhpXmlRpc\Helper\XMLParser;
7
8
/**
9
 * A helper class to easily convert between Value objects and php native values
10
 * @todo implement an interface
11
 * @todo add class constants for the options values
12
 */
13
class Encoder
14
{
15
    /**
16
     * Takes an xmlrpc value in object format and translates it into native PHP types.
17
     *
18
     * Works with xmlrpc requests objects as input, too.
19
     *
20
     * Given proper options parameter, can rebuild generic php object instances (provided those have been encoded to
21
     * xmlrpc format using a corresponding option in php_xmlrpc_encode())
22
     * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
23
     * This means that the remote communication end can decide which php code will get executed on your server, leaving
24
     * the door possibly open to 'php-injection' style of attacks (provided you have some classes defined on your server
25
     * that might wreak havoc if instances are built outside an appropriate context).
26
     * Make sure you trust the remote server/client before enabling this!
27
     *
28
     * @author Dan Libby ([email protected])
29
     *
30
     * @param Value|Request $xmlrpcVal
31
     * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php
32
     *                       objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects
33
     *
34
     * @return mixed
35
     */
36 234
    public function decode($xmlrpcVal, $options = array())
37
    {
38 234
        switch ($xmlrpcVal->kindOf()) {
39 234
            case 'scalar':
40 234
                if (in_array('extension_api', $options)) {
41
                    $val = reset($xmlrpcVal->me);
0 ignored issues
show
Bug introduced by
The property me does not seem to exist on PhpXmlRpc\Request.
Loading history...
42
                    $typ = key($xmlrpcVal->me);
43
                    switch ($typ) {
44
                        case 'dateTime.iso8601':
45
                            $xmlrpcVal = array(
46
                                'xmlrpc_type' => 'datetime',
47
                                'scalar' => $val,
48
                                'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($val)
49
                            );
50
                            return (object)$xmlrpcVal;
51
                        case 'base64':
52
                            $xmlrpcVal = array(
53
                                'xmlrpc_type' => 'base64',
54
                                'scalar' => $val
55
                            );
56
                            return (object)$xmlrpcVal;
57
                        case 'string':
58
                            if (isset($options['extension_api_encoding'])) {
59
                                $dval = @iconv('UTF-8', $options['extension_api_encoding'], $val);
60
                                if ($dval !== false) {
61
                                    return $dval;
62
                                }
63
                            }
64
                            //return $val;
65
                            // break through voluntarily
66
                        default:
67
                            return $val;
68
                    }
69
                }
70 234
                if (in_array('dates_as_objects', $options) && $xmlrpcVal->scalartyp() == 'dateTime.iso8601') {
0 ignored issues
show
Bug introduced by
The method scalartyp() does not exist on PhpXmlRpc\Request. ( Ignorable by Annotation )

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

70
                if (in_array('dates_as_objects', $options) && $xmlrpcVal->/** @scrutinizer ignore-call */ scalartyp() == 'dateTime.iso8601') {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
71
                    // we return a Datetime object instead of a string since now the constructor of xmlrpc value accepts
72
                    // safely strings, ints and datetimes, we cater to all 3 cases here
73
                    $out = $xmlrpcVal->scalarval();
0 ignored issues
show
Bug introduced by
The method scalarval() does not exist on PhpXmlRpc\Request. ( Ignorable by Annotation )

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

73
                    /** @scrutinizer ignore-call */ 
74
                    $out = $xmlrpcVal->scalarval();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
74
                    if (is_string($out)) {
75
                        $out = strtotime($out);
76
                    }
77
                    if (is_int($out)) {
78
                        $result = new \DateTime();
79
                        $result->setTimestamp($out);
80
81
                        return $result;
82
                    } elseif (is_a($out, 'DateTimeInterface')) {
83
                        return $out;
84
                    }
85
                }
86 234
                return $xmlrpcVal->scalarval();
87
88 232
            case 'array':
89 96
                $arr = array();
90 96
                foreach($xmlrpcVal as $value) {
91 96
                    $arr[] = $this->decode($value, $options);
92
                }
93 96
                return $arr;
94
95 175
            case 'struct':
96
                // If user said so, try to rebuild php objects for specific struct vals.
97
                /// @todo should we raise a warning for class not found?
98
                // shall we check for proper subclass of xmlrpc value instead of presence of _php_class to detect
99
                // what we can do?
100 59
                if (in_array('decode_php_objs', $options) && $xmlrpcVal->_php_class != ''
0 ignored issues
show
Bug introduced by
The property _php_class does not seem to exist on PhpXmlRpc\Request.
Loading history...
101 39
                    && class_exists($xmlrpcVal->_php_class)
0 ignored issues
show
Bug introduced by
It seems like $xmlrpcVal->_php_class can also be of type null; however, parameter $class of class_exists() 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

101
                    && class_exists(/** @scrutinizer ignore-type */ $xmlrpcVal->_php_class)
Loading history...
102
                ) {
103 20
                    $obj = @new $xmlrpcVal->_php_class();
104 20
                    foreach ($xmlrpcVal as $key => $value) {
105 20
                        $obj->$key = $this->decode($value, $options);
106
                    }
107 20
                    return $obj;
108
                } else {
109 40
                    $arr = array();
110 40
                    foreach ($xmlrpcVal as $key => $value) {
111 40
                        $arr[$key] = $this->decode($value, $options);
112
                    }
113 40
                    return $arr;
114
                }
115
116 117
            case 'msg':
117 117
                $paramCount = $xmlrpcVal->getNumParams();
0 ignored issues
show
Bug introduced by
The method getNumParams() does not exist on PhpXmlRpc\Value. ( Ignorable by Annotation )

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

117
                /** @scrutinizer ignore-call */ 
118
                $paramCount = $xmlrpcVal->getNumParams();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
118 117
                $arr = array();
119 117
                for ($i = 0; $i < $paramCount; $i++) {
120 117
                    $arr[] = $this->decode($xmlrpcVal->getParam($i), $options);
0 ignored issues
show
Bug introduced by
The method getParam() does not exist on PhpXmlRpc\Value. ( Ignorable by Annotation )

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

120
                    $arr[] = $this->decode($xmlrpcVal->/** @scrutinizer ignore-call */ getParam($i), $options);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
121
                }
122 117
                return $arr;
123
124
            /// @todo throw on unsupported type
125
        }
126
    }
127
128
    /**
129
     * Takes native php types and encodes them into xmlrpc PHP object format.
130
     * It will not re-encode xmlrpc value objects.
131
     *
132
     * Feature creep -- could support more types via optional type argument
133
     * (string => datetime support has been added, ??? => base64 not yet)
134
     *
135
     * If given a proper options parameter, php object instances will be encoded into 'special' xmlrpc values, that can
136
     * later be decoded into php objects by calling php_xmlrpc_decode() with a corresponding option
137
     *
138
     * @author Dan Libby ([email protected])
139
     *
140
     * @param mixed $phpVal the value to be converted into an xmlrpc value object
141
     * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
142
     *
143
     * @return Value
144
     */
145 219
    public function encode($phpVal, $options = array())
146
    {
147 219
        $type = gettype($phpVal);
148 219
        switch ($type) {
149 219
            case 'string':
150
                /// @todo should we be stricter in the accepted dates (ie. reject more of invalid days & times)?
151 214
                if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $phpVal)) {
152 1
                    $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDateTime);
153
                } else {
154 214
                    $xmlrpcVal = new Value($phpVal, Value::$xmlrpcString);
155
                }
156 214
                break;
157 105
            case 'integer':
158 100
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcInt);
159 100
                break;
160 27
            case 'double':
161 1
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDouble);
162 1
                break;
163
            // Add support for encoding/decoding of booleans, since they are supported in PHP
164 27
            case 'boolean':
165 1
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcBoolean);
166 1
                break;
167 27
            case 'array':
168
                // PHP arrays can be encoded to either xmlrpc structs or arrays, depending on whether they are hashes
169
                // or plain 0..n integer indexed
170
                // A shorter one-liner would be
171
                // $tmp = array_diff(array_keys($phpVal), range(0, count($phpVal)-1));
172
                // but execution time skyrockets!
173 23
                $j = 0;
174 23
                $arr = array();
175 23
                $ko = false;
176 23
                foreach ($phpVal as $key => $val) {
177 23
                    $arr[$key] = $this->encode($val, $options);
178 23
                    if (!$ko && $key !== $j) {
179 22
                        $ko = true;
180
                    }
181 23
                    $j++;
182
                }
183 23
                if ($ko) {
184 22
                    $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct);
185
                } else {
186 3
                    $xmlrpcVal = new Value($arr, Value::$xmlrpcArray);
187
                }
188 23
                break;
189 5
            case 'object':
190 3
                if (is_a($phpVal, 'PhpXmlRpc\Value')) {
191 1
                    $xmlrpcVal = $phpVal;
192 2
                } elseif (is_a($phpVal, 'DateTimeInterface')) {
193 1
                    $xmlrpcVal = new Value($phpVal->format('Ymd\TH:i:s'), Value::$xmlrpcDateTime);
194 1
                } elseif (in_array('extension_api', $options) && $phpVal instanceof \stdClass && isset($phpVal->xmlrpc_type)) {
195
                    // Handle the 'pre-converted' base64 and datetime values
196
                    if (isset($phpVal->scalar)) {
197
                        switch ($phpVal->xmlrpc_type) {
198
                            case 'base64':
199
                                $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcBase64);
200
                                break;
201
                            case 'datetime':
202
                                $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcDateTime);
203
                                break;
204
                            default:
205
                                $xmlrpcVal = new Value();
206
                        }
207
                    } else {
208
                        $xmlrpcVal = new Value();
209
                    }
210
211
                } else {
212 1
                    $arr = array();
213 1
                    foreach($phpVal as $k => $v) {
214 1
                        $arr[$k] = $this->encode($v, $options);
215
                    }
216 1
                    $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct);
217 1
                    if (in_array('encode_php_objs', $options)) {
218
                        // let's save original class name into xmlrpc value:
219
                        // might be useful later on...
220 1
                        $xmlrpcVal->_php_class = get_class($phpVal);
221
                    }
222
                }
223 3
                break;
224 2
            case 'NULL':
225 2
                if (in_array('extension_api', $options)) {
226
                    $xmlrpcVal = new Value('', Value::$xmlrpcString);
227 2
                } elseif (in_array('null_extension', $options)) {
228
                    $xmlrpcVal = new Value('', Value::$xmlrpcNull);
229
                } else {
230 2
                    $xmlrpcVal = new Value();
231
                }
232 2
                break;
233
            case 'resource':
234
                if (in_array('extension_api', $options)) {
235
                    $xmlrpcVal = new Value((int)$phpVal, Value::$xmlrpcInt);
236
                } else {
237
                    $xmlrpcVal = new Value();
238
                }
239
                break;
240
            // catch "user function", "unknown type"
241
            default:
242
                // giancarlo pinerolo <[email protected]>
243
                // it has to return an empty object in case, not a boolean.
244
                $xmlrpcVal = new Value();
245
                break;
246
        }
247
248 219
        return $xmlrpcVal;
249
    }
250
251
    /**
252
     * Convert the xml representation of a method response, method request or single
253
     * xmlrpc value into the appropriate object (a.k.a. deserialize).
254
     *
255
     * @todo is this a good name/class for this method? It does something quite different from 'decode' after all
256
     *       (returning objects vs returns plain php values)... In fact it belongs rather to a Parser class
257
     *
258
     * @param string $xmlVal
259
     * @param array $options
260
     *
261
     * @return Value|Request|Response|false false on error, or an instance of either Value, Request or Response
262
     */
263 3
    public function decodeXml($xmlVal, $options = array())
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

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

263
    public function decodeXml($xmlVal, /** @scrutinizer ignore-unused */ $options = array())

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
264
    {
265
        // 'guestimate' encoding
266 3
        $valEncoding = XMLParser::guessEncoding('', $xmlVal);
267 3
        if ($valEncoding != '') {
268
269
            // Since parsing will fail if
270
            // - charset is not specified in the xml prologue,
271
            // - the encoding is not UTF8 and
272
            // - there are non-ascii chars in the text,
273
            // we try to work round that...
274
            // The following code might be better for mb_string enabled installs, but makes the lib about 200% slower...
275
            //if (!is_valid_charset($valEncoding, array('UTF-8'))
276 3
            if (!in_array($valEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($xmlVal)) {
277
                if ($valEncoding == 'ISO-8859-1') {
278
                    $xmlVal = utf8_encode($xmlVal);
279
                } else {
280
                    if (extension_loaded('mbstring')) {
281
                        $xmlVal = mb_convert_encoding($xmlVal, 'UTF-8', $valEncoding);
282
                    } else {
283
                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding);
284
                    }
285
                }
286
            }
287
        }
288
289
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8!
290 3
        if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
291
            /// @todo emit a warning
292
            $parserOptions = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
293
        } else {
294 3
            $parserOptions = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
295
        }
296
297 3
        $xmlRpcParser = new XMLParser($parserOptions);
298 3
        $xmlRpcParser->parse($xmlVal, XMLParser::RETURN_XMLRPCVALS, XMLParser::ACCEPT_REQUEST | XMLParser::ACCEPT_RESPONSE | XMLParser::ACCEPT_VALUE | XMLParser::ACCEPT_FAULT);
0 ignored issues
show
Bug introduced by
It seems like $xmlVal can also be of type array; however, parameter $data of PhpXmlRpc\Helper\XMLParser::parse() 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

298
        $xmlRpcParser->parse(/** @scrutinizer ignore-type */ $xmlVal, XMLParser::RETURN_XMLRPCVALS, XMLParser::ACCEPT_REQUEST | XMLParser::ACCEPT_RESPONSE | XMLParser::ACCEPT_VALUE | XMLParser::ACCEPT_FAULT);
Loading history...
299
300 3
        if ($xmlRpcParser->_xh['isf'] > 1) {
301
            // test that $xmlrpc->_xh['value'] is an obj, too???
302
303
            Logger::instance()->errorLog($xmlRpcParser->_xh['isf_reason']);
304
305
            return false;
306
        }
307
308 3
        switch ($xmlRpcParser->_xh['rt']) {
309 3
            case 'methodresponse':
310 3
                $v = $xmlRpcParser->_xh['value'];
311 3
                if ($xmlRpcParser->_xh['isf'] == 1) {
312
                    /** @var Value $vc */
313
                    $vc = $v['faultCode'];
314
                    /** @var Value $vs */
315
                    $vs = $v['faultString'];
316
                    $r = new Response(0, $vc->scalarval(), $vs->scalarval());
317
                } else {
318 3
                    $r = new Response($v);
319
                }
320 3
                return $r;
321
322 1
            case 'methodcall':
323 1
                $req = new Request($xmlRpcParser->_xh['method']);
0 ignored issues
show
Bug introduced by
$xmlRpcParser->_xh['method'] of type false is incompatible with the type string expected by parameter $methodName of PhpXmlRpc\Request::__construct(). ( Ignorable by Annotation )

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

323
                $req = new Request(/** @scrutinizer ignore-type */ $xmlRpcParser->_xh['method']);
Loading history...
324 1
                for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
325 1
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
326
                }
327 1
                return $req;
328
329 1
            case 'value':
330 1
                return $xmlRpcParser->_xh['value'];
331
332
            case 'fault':
333
                // EPI api emulation
334
                $v = $xmlRpcParser->_xh['value'];
335
                // use a known error code
336
                /** @var Value $vc */
337
                $vc = isset($v['faultCode']) ? $v['faultCode']->scalarval() : PhpXmlRpc::$xmlrpcerr['invalid_return'];
338
                /** @var Value $vs */
339
                $vs = isset($v['faultString']) ? $v['faultString']->scalarval() : '';
340
                if (!is_int($vc) || $vc == 0) {
0 ignored issues
show
introduced by
The condition is_int($vc) is always false.
Loading history...
341
                    $vc = PhpXmlRpc::$xmlrpcerr['invalid_return'];
342
                }
343
                return new Response(0, $vc, $vs);
0 ignored issues
show
Bug introduced by
$vs of type PhpXmlRpc\Value is incompatible with the type string expected by parameter $fString of PhpXmlRpc\Response::__construct(). ( Ignorable by Annotation )

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

343
                return new Response(0, $vc, /** @scrutinizer ignore-type */ $vs);
Loading history...
344
            default:
345
                return false;
346
        }
347
    }
348
}
349