Passed
Push — master ( da2c1e...110169 )
by Gaetano
04:55 queued 01:13
created

Encoder::decode()   D

Complexity

Conditions 20
Paths 19

Size

Total Lines 80
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 54.0735

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 20
eloc 53
c 3
b 0
f 0
nc 19
nop 2
dl 0
loc 80
ccs 28
cts 50
cp 0.56
crap 54.0735
rs 4.1666

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
                        default:
58
                            return $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

58
                            return $xmlrpcVal->/** @scrutinizer ignore-call */ 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...
59
                    }
60
                }
61 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

61
                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...
62
                    // we return a Datetime object instead of a string since now the constructor of xmlrpc value accepts
63
                    // safely strings, ints and datetimes, we cater to all 3 cases here
64
                    $out = $xmlrpcVal->scalarval();
65
                    if (is_string($out)) {
66
                        $out = strtotime($out);
67
                    }
68
                    if (is_int($out)) {
69
                        $result = new \DateTime();
70
                        $result->setTimestamp($out);
71
72
                        return $result;
73
                    } elseif (is_a($out, 'DateTimeInterface')) {
74
                        return $out;
75
                    }
76
                }
77
78 234
                return $xmlrpcVal->scalarval();
79 232
            case 'array':
80 96
                $arr = array();
81 96
                foreach($xmlrpcVal as $value) {
82 96
                    $arr[] = $this->decode($value, $options);
83
                }
84
85 96
                return $arr;
86 175
            case 'struct':
87
                // If user said so, try to rebuild php objects for specific struct vals.
88
                /// @todo should we raise a warning for class not found?
89
                // shall we check for proper subclass of xmlrpc value instead of presence of _php_class to detect
90
                // what we can do?
91 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...
92 39
                    && class_exists($xmlrpcVal->_php_class)
93
                ) {
94 20
                    $obj = @new $xmlrpcVal->_php_class();
95 20
                    foreach ($xmlrpcVal as $key => $value) {
96 20
                        $obj->$key = $this->decode($value, $options);
97
                    }
98
99 20
                    return $obj;
100
                } else {
101 40
                    $arr = array();
102 40
                    foreach ($xmlrpcVal as $key => $value) {
103 40
                        $arr[$key] = $this->decode($value, $options);
104
                    }
105
106 40
                    return $arr;
107
                }
108 117
            case 'msg':
109 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

109
                /** @scrutinizer ignore-call */ 
110
                $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...
110 117
                $arr = array();
111 117
                for ($i = 0; $i < $paramCount; $i++) {
112 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

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

254
    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...
255
    {
256
        // 'guestimate' encoding
257 3
        $valEncoding = XMLParser::guessEncoding('', $xmlVal);
258 3
        if ($valEncoding != '') {
259
260
            // Since parsing will fail if
261
            // - charset is not specified in the xml prologue,
262
            // - the encoding is not UTF8 and
263
            // - there are non-ascii chars in the text,
264
            // we try to work round that...
265
            // The following code might be better for mb_string enabled installs, but makes the lib about 200% slower...
266
            //if (!is_valid_charset($valEncoding, array('UTF-8'))
267 3
            if (!in_array($valEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($xmlVal)) {
268
                if ($valEncoding == 'ISO-8859-1') {
269
                    $xmlVal = utf8_encode($xmlVal);
270
                } else {
271
                    if (extension_loaded('mbstring')) {
272
                        $xmlVal = mb_convert_encoding($xmlVal, 'UTF-8', $valEncoding);
273
                    } else {
274
                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding);
275
                    }
276
                }
277
            }
278
        }
279
280
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8!
281 3
        if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
282
            $parserOptions = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
283
        } else {
284 3
            $parserOptions = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
285
        }
286
287 3
        $xmlRpcParser = new XMLParser($parserOptions);
288 3
        $xmlRpcParser->parse($xmlVal, XMLParser::RETURN_XMLRPCVALS, XMLParser::ACCEPT_REQUEST | XMLParser::ACCEPT_RESPONSE | XMLParser::ACCEPT_VALUE | XMLParser::ACCEPT_FAULT);
289
290 3
        if ($xmlRpcParser->_xh['isf'] > 1) {
291
            // test that $xmlrpc->_xh['value'] is an obj, too???
292
293
            Logger::instance()->errorLog($xmlRpcParser->_xh['isf_reason']);
294
295
            return false;
296
        }
297
298 3
        switch ($xmlRpcParser->_xh['rt']) {
299 3
            case 'methodresponse':
300 3
                $v = $xmlRpcParser->_xh['value'];
301 3
                if ($xmlRpcParser->_xh['isf'] == 1) {
302
                    /** @var Value $vc */
303
                    $vc = $v['faultCode'];
304
                    /** @var Value $vs */
305
                    $vs = $v['faultString'];
306
                    $r = new Response(0, $vc->scalarval(), $vs->scalarval());
307
                } else {
308 3
                    $r = new Response($v);
309
                }
310
311 3
                return $r;
312 1
            case 'methodcall':
313 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

313
                $req = new Request(/** @scrutinizer ignore-type */ $xmlRpcParser->_xh['method']);
Loading history...
314 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...
315 1
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
316
                }
317
318 1
                return $req;
319 1
            case 'value':
320 1
                return $xmlRpcParser->_xh['value'];
321
            case 'fault':
322
                // EPI api emulation
323
                $v = $xmlRpcParser->_xh['value'];
324
                // use a known error code
325
                /** @var Value $vc */
326
                $vc = isset($v['faultCode']) ? $v['faultCode']->scalarval() : PhpXmlRpc::$xmlrpcerr['invalid_return'];
327
                /** @var Value $vs */
328
                $vs = isset($v['faultString']) ? $v['faultString']->scalarval() : '';
329
                if (!is_int($vc) || $vc == 0) {
0 ignored issues
show
introduced by
The condition is_int($vc) is always false.
Loading history...
330
                    $vc = PhpXmlRpc::$xmlrpcerr['invalid_return'];
331
                }
332
                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

332
                return new Response(0, $vc, /** @scrutinizer ignore-type */ $vs);
Loading history...
333
            default:
334
                return false;
335
        }
336
    }
337
}
338