Completed
Push — master ( 650880...84b6a6 )
by Markus
21s
created

Response::unwrapEncryptedMsg()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
ccs 3
cts 4
cp 0.75
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 2.0625
1
<?php
2
3
namespace Fhp\Response;
4
5
use Fhp\Message\AbstractMessage;
6
use Fhp\Segment\AbstractSegment;
7
use Fhp\Segment\NameMapping;
8
9
/**
10
 * Class Response
11
 *
12
 * @package Fhp\Response
13
 */
14
class Response
15
{
16
17
    /** @var string */
18
    public $rawResponse;
19
20
    /** @var string */
21
    protected $response;
22
23
    /** @var array */
24
    protected $segments = array();
25
26
    /** @var string */
27
    protected $dialogId;
28
29
    /** @var string */
30
    protected $systemId;
31
32
    /**
33
     * Response constructor.
34
     *
35
     * @param string $rawResponse
36
     */
37 1
    public function __construct($rawResponse)
38
    {
39 1
        if ($rawResponse instanceof Initialization) {
40
            $rawResponse = $rawResponse->rawResponse;
41
        }
42
43 1
        $this->rawResponse = $rawResponse;
44 1
        $this->response = $this->unwrapEncryptedMsg($rawResponse);
45 1
        $this->segments = preg_split("#'(?=[A-Z]{4,}:\d|')#", $rawResponse);
46 1
    }
47
48
    /**
49
     * Extracts dialog ID from response.
50
     *
51
     * @return string|null
52
     * @throws \Exception
53
     */
54
    public function getDialogId()
55
    {
56
        $segment = $this->findSegment('HNHBK');
57
58
        if (null == $segment) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $segment of type array|null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
59
            throw new \Exception('Could not find element HNHBK. Invalid response?');
60
        }
61
62
        return $this->getSegmentIndex(4, $segment);
63
    }
64
65
    /**
66
     * Extracts bank name from response.
67
     *
68
     * @return string|null
69
     */
70
    public function getBankName()
71
    {
72
        $bankName = null;
73
        $segment = $this->findSegment('HIBPA');
74
        if (null != $segment) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $segment of type array|null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
75
            $split = $this->splitSegment($segment);
76
            if (isset($split[3])) {
77
                $bankName = $split[3];
78
            }
79
        }
80
81
        return $bankName;
82
    }
83
84
    /**
85
     * Some kind of HBCI pagination.
86
     *
87
     * @param AbstractMessage $message
88
     *
89
     * @return array
90
     */
91
    public function getTouchDowns(AbstractMessage $message)
92
    {
93
        $touchdown = array();
94
        $messageSegments = $message->getEncryptedSegments();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fhp\Message\AbstractMessage as the method getEncryptedSegments() does only exist in the following sub-classes of Fhp\Message\AbstractMessage: Fhp\Message\Message. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
95
        /** @var AbstractSegment $msgSeg */
96
        foreach ($messageSegments as $msgSeg) {
97
            $segment = $this->findSegmentForReference('HIRMS', $msgSeg);
98
            if (null != $segment) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $segment of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
99
                $parts = $this->splitSegment($segment);
100
                // remove header
101
                array_shift($parts);
102
                foreach ($parts as $p) {
103
                    $pSplit = $this->splitDeg($p);
104
                    if ($pSplit[0] == 3040) {
105
                        $td = $pSplit[3];
106
                        $touchdown[$msgSeg->getName()] = $td;
107
                    }
108
                }
109
            }
110
        }
111
112
        return $touchdown;
113
    }
114
115
    /**
116
     * Extracts supported TAN mechanisms from response.
117
     *
118
     * @return array|bool
119
     */
120
    public function getSupportedTanMechanisms()
121
    {
122
        $segments = $this->findSegments('HIRMS');
123
        // @todo create method to get reference element from request
124
        foreach ($segments as $segment) {
0 ignored issues
show
Bug introduced by
The expression $segments of type array|null|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
125
            $segment = $this->splitSegment($segment);
126
            array_shift($segment);
127
            foreach ($segment as $seg) {
128
                list($id, $msg) = explode('::', $seg, 2);
129
                if ("3920" == $id) {
130
                    if (preg_match_all('/\d{3}/', $msg, $matches)) {
131
                        return $matches[0];
132
                    }
133
                }
134
            }
135
        }
136
137
        return false;
138
    }
139
140
    /**
141
     * @return string
142
     */
143
    public function getHksalMaxVersion()
144
    {
145
        return $this->getSegmentMaxVersion('HISALS');
146
    }
147
148
    /**
149
     * @return string
150
     */
151
    public function getHkkazMaxVersion()
152
    {
153
        return $this->getSegmentMaxVersion('HIKAZS');
154
    }
155
156
    /**
157
     * Checks if request / response was successful.
158
     *
159
     * @return bool
160
     */
161
    public function isSuccess()
162
    {
163
        $summary = $this->getMessageSummary();
164
165
        foreach ($summary as $code => $message) {
166
            if ("9" == substr($code, 0, 1)) {
167
                return false;
168
            }
169
        }
170
171
        return true;
172
    }
173
174
    /**
175
     * @return array
176
     * @throws \Exception
177
     */
178
    public function getMessageSummary()
179
    {
180
        return $this->getSummaryBySegment('HIRMG');
181
    }
182
183
    /**
184
     * @return array
185
     * @throws \Exception
186
     */
187
    public function getSegmentSummary()
188
    {
189
        return $this->getSummaryBySegment('HIRMS');
190
    }
191
192
    /**
193
     * @param string $name
194
     *
195
     * @return array
196
     * @throws \Exception
197
     */
198
    protected function getSummaryBySegment($name)
199
    {
200
        if (!in_array($name, array('HIRMS', 'HIRMG'))) {
201
            throw new \Exception('Invalid segment for message summary. Only HIRMS and HIRMG supported');
202
        }
203
204
        $result = array();
205
        $segment = $this->findSegment($name);
206
        $segment = $this->splitSegment($segment);
207
        array_shift($segment);
208
        foreach ($segment as $de) {
209
            $de = $this->splitDeg($de);
210
            $result[$de[0]] = $de[2];
211
        }
212
213
        return $result;
214
    }
215
216
    /**
217
     * @param string $segmentName
218
     *
219
     * @return string
220
     */
221
    public function getSegmentMaxVersion($segmentName)
222
    {
223
        $version = "3";
224
        $segments = $this->findSegments($segmentName);
225
        foreach ($segments as $s) {
0 ignored issues
show
Bug introduced by
The expression $segments of type array|null|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
226
            $parts = $this->splitSegment($s);
227
            $segmentHeader = $this->splitDeg($parts[0]);
228
            $curVersion = $segmentHeader[2];
229
            if ($curVersion > $version) {
230
                $version = $curVersion;
231
            }
232
        }
233
234
        return $version;
235
    }
236
237
    /**
238
     * @return string
239
     * @throws \Exception
240
     */
241
    public function getSystemId()
242
    {
243
        $segment = $this->findSegment('HISYN');
244
245
        if (!preg_match('/HISYN:\d+:\d+:\d+\+(.+)/', $segment, $matches)) {
246
            throw new \Exception('Could not determine system id.');
247
        }
248
249
        return $matches[1];
250
    }
251
252
    /**
253
     * @param bool $translateCodes
254
     *
255
     * @return string
256
     */
257 View Code Duplication
    public function humanReadable($translateCodes = false)
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...
258
    {
259
        return str_replace(
260
            array("'", '+'),
261
            array(PHP_EOL, PHP_EOL . "  "),
262
            $translateCodes
263
                ? NameMapping::translateResponse($this->rawResponse)
264
                : $this->rawResponse
265
        );
266
    }
267
268
    /**
269
     * @param string          $name
270
     * @param AbstractSegment $reference
271
     *
272
     * @return string|null
273
     */
274
    protected function findSegmentForReference($name, AbstractSegment $reference)
275
    {
276
        $result = null;
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
277
        $segments = $this->findSegments($name);
278
        foreach ($segments as $seg) {
0 ignored issues
show
Bug introduced by
The expression $segments of type array|null|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
279
            $segSplit = $this->splitSegment($seg);
280
            $segSplit = array_shift($segSplit);
281
            $segSplit = $this->splitDeg($segSplit);
282
            if ($segSplit[3] == $reference->getSegmentNumber()) {
283
                return $seg;
284
            }
285
        }
286
287
        return null;
288
    }
289
290
    /**
291
     * @param string $name
292
     *
293
     * @return array|null|string
294
     */
295
    protected function findSegment($name)
296
    {
297
        return $this->findSegments($name, true);
298
    }
299
300
    /**
301
     * @param string $name
302
     * @param bool   $one
303
     *
304
     * @return array|null|string
305
     */
306
    protected function findSegments($name, $one = false)
307
    {
308
        $found = $one ? null : array();
309
310
        foreach ($this->segments as $segment) {
311
            $split = explode(':', $segment, 2);
312
313
            if ($split[0] == $name) {
314
                if ($one) {
315
                    return $segment;
316
                }
317
                $found[] = $segment;
318
            }
319
        }
320
321
        return $found;
322
    }
323
324
    /**
325
     * @param $segment
326
     *
327
     * @return array
328
     */
329 1
    protected function splitSegment($segment)
330
    {
331 1
        $parts = preg_split('/\+(?<!\?\+)/', $segment);
332
333 1
        foreach ($parts as &$part) {
334 1
            $part = str_replace('?+', '+', $part);
335 1
        }
336
337 1
        return $parts;
338
    }
339
340
    /**
341
     * @param $deg
342
     *
343
     * @return array
344
     */
345
    protected function splitDeg($deg)
346
    {
347
        return explode(':', $deg);
348
    }
349
350
    /**
351
     * @param int $idx
352
     * @param     $segment
353
     *
354
     * @return string|null
355
     */
356
    protected function getSegmentIndex($idx, $segment)
357
    {
358
        $segment = $this->splitSegment($segment);
359
        if (isset($segment[$idx - 1])) {
360
            return $segment[$idx - 1];
361
        }
362
363
        return null;
364
    }
365
366
    /**
367
     * @param string $response
368
     *
369
     * @return string
370
     */
371 1
    protected function unwrapEncryptedMsg($response)
372
    {
373 1
        if (preg_match('/HNVSD:\d+:\d+\+@\d+@(.+)\'\'/', $response, $matches)) {
374
            return $matches[1];
375
        }
376
377 1
        return $response;
378
    }
379
}
380