Passed
Push — master ( 3fbb2c...cdd150 )
by Gaetano
07:31
created

Encoder   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 322
Duplicated Lines 0 %

Test Coverage

Coverage 61.25%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 174
c 4
b 0
f 0
dl 0
loc 322
ccs 98
cts 160
cp 0.6125
rs 3.12
wmc 66

3 Methods

Rating   Name   Duplication   Size   Complexity  
D decode() 0 80 20
D encode() 0 104 28
D decodeXml() 0 81 18

How to fix   Complexity   

Complex Class

Complex classes like Encoder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Encoder, and based on these observations, apply Extract Interface, too.

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
 */
12
class Encoder
13
{
14
    /**
15
     * Takes an xmlrpc value in object format and translates it into native PHP types.
16
     *
17
     * Works with xmlrpc requests objects as input, too.
18
     *
19
     * Given proper options parameter, can rebuild generic php object instances (provided those have been encoded to
20
     * xmlrpc format using a corresponding option in php_xmlrpc_encode())
21
     * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
22
     * This means that the remote communication end can decide which php code will get executed on your server, leaving
23
     * the door possibly open to 'php-injection' style of attacks (provided you have some classes defined on your server
24
     * that might wreak havoc if instances are built outside an appropriate context).
25
     * Make sure you trust the remote server/client before eanbling this!
26
     *
27
     * @author Dan Libby ([email protected])
28
     *
29
     * @param Value|Request $xmlrpcVal
30
     * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php
31
     *                       objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects
32
     *
33
     * @return mixed
34
     */
35 234
    public function decode($xmlrpcVal, $options = array())
36
    {
37 234
        switch ($xmlrpcVal->kindOf()) {
38 234
            case 'scalar':
39 234
                if (in_array('extension_api', $options)) {
40
                    $val = reset($xmlrpcVal->me);
0 ignored issues
show
Bug introduced by
The property me does not seem to exist on PhpXmlRpc\Request.
Loading history...
41
                    $typ = key($xmlrpcVal->me);
42
                    switch ($typ) {
43
                        case 'dateTime.iso8601':
44
                            $xmlrpcVal = array(
45
                                'xmlrpc_type' => 'datetime',
46
                                'scalar' => $val,
47
                                'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($val)
48
                            );
49
                            return (object)$xmlrpcVal;
50
                        case 'base64':
51
                            $xmlrpcVal = array(
52
                                'xmlrpc_type' => 'base64',
53
                                'scalar' => $val
54
                            );
55
                            return (object)$xmlrpcVal;
56
                        default:
57
                            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

57
                            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...
58
                    }
59
                }
60 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

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

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

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

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

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

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