Completed
Push — master ( db6c33...438487 )
by Anton
02:19
created

ValidatorV1::analyzeSequenceId()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 3
nop 1
1
<?php
2
3
namespace Covery\Client\Envelopes;
4
5
use Covery\Client\EnvelopeInterface;
6
use Covery\Client\EnvelopeValidationException;
7
use Covery\Client\IdentityNodeInterface;
8
9
class ValidatorV1
10
{
11
    private static $dataTypes = array(
12
        'billing_address' => 'string',
13
        'billing_city' => 'string',
14
        'billing_country' => 'string',
15
        'billing_firstname' => 'string',
16
        'billing_lastname' => 'string',
17
        'billing_fullname' => 'string',
18
        'billing_state' => 'string',
19
        'billing_zip' => 'string',
20
        'card_id' => 'string',
21
        'card_last4' => 'string',
22
        'country' => 'string',
23
        'cpu_class' => 'string',
24
        'device_fingerprint' => 'string',
25
        'firstname' => 'string',
26
        'gender' => 'string',
27
        'language' => 'string',
28
        'language_browser' => 'string',
29
        'language_system' => 'string',
30
        'language_user' => 'string',
31
        'languages' => 'string',
32
        'lastname' => 'string',
33
        'login_user_agent' => 'string',
34
        'os' => 'string',
35
        'payment_method' => 'string',
36
        'payment_mid' => 'string',
37
        'payment_system' => 'string',
38
        'product_description' => 'string',
39
        'product_name' => 'string',
40
        'registration_useragent' => 'string',
41
        'screen_orientation' => 'string',
42
        'screen_resolution' => 'string',
43
        'social_type' => 'string',
44
        'transaction_currency' => 'string',
45
        'transaction_id' => 'string',
46
        'transaction_mode' => 'string',
47
        'transaction_type' => 'string',
48
        'user_agent' => 'string',
49
        'user_merchant_id' => 'string',
50
        'user_name' => 'string',
51
        'website_url' => 'string',
52
        'transaction_source' => 'string',
53
        'ip' => 'string',
54
        'merchant_ip' => 'string',
55
        'real_ip' => 'string',
56
        'email' => 'string',
57
        'phone' => 'string',
58
        'age' => 'int',
59
        'card_bin' => 'int',
60
        'confirmation_timestamp' => 'int',
61
        'expiration_month' => 'int',
62
        'expiration_year' => 'int',
63
        'login_timestamp' => 'int',
64
        'registration_timestamp' => 'int',
65
        'timezone_offset' => 'int',
66
        'transaction_timestamp' => 'int',
67
        'product_quantity' => 'float',
68
        'transaction_amount' => 'float',
69
        'transaction_amount_converted' => 'float',
70
        'ajax_validation' => 'bool',
71
        'cookie_enabled' => 'bool',
72
        'do_not_track' => 'bool',
73
        'email_confirmed' => 'bool',
74
        'login_failed' => 'bool',
75
        'phone_confirmed' => 'bool',
76
    );
77
78
    private static $types = array(
79
        'confirmation' => array(
80
            'mandatory' => array('confirmation_timestamp', 'user_merchant_id'),
81
            'optional' => array('email_confirmed', 'phone_confirmed'),
82
        ),
83
        'login' => array(
84
            'mandatory' => array('login_timestamp', 'user_merchant_id'),
85
            'optional' => array('email', 'login_failed', 'phone')
86
        ),
87
        'registration' => array(
88
            'mandatory' => array('registration_timestamp', 'user_merchant_id'),
89
            'optional' => array(
90
                'age',
91
                'country',
92
                'email',
93
                'firstname',
94
                'gender',
95
                'lastname',
96
                'phone',
97
                'social_type',
98
                'user_name',
99
            ),
100
        ),
101
        'transaction' => array(
102
            'mandatory' => array(
103
                'transaction_amount',
104
                'transaction_currency',
105
                'transaction_id',
106
                'transaction_mode',
107
                'transaction_timestamp',
108
                'transaction_type',
109
                'card_bin',
110
                'card_id',
111
                'card_last4',
112
                'expiration_month',
113
                'expiration_year',
114
                'user_merchant_id',
115
            ),
116
            'optional' => array(
117
                'age',
118
                'country',
119
                'email',
120
                'firstname',
121
                'gender',
122
                'lastname',
123
                'phone',
124
                'user_name',
125
                'payment_method',
126
                'payment_mid',
127
                'payment_system',
128
                'transaction_amount_converted',
129
                'transaction_source',
130
                'billing_address',
131
                'billing_city',
132
                'billing_country',
133
                'billing_firstname',
134
                'billing_lastname',
135
                'billing_fullname',
136
                'billing_state',
137
                'billing_zip',
138
                'product_description',
139
                'product_name',
140
                'product_quantity',
141
                'website_url',
142
                'merchant_ip',
143
            )
144
        ),
145
    );
146
147
    /**
148
     * Analyzes SequenceID
149
     *
150
     * @param string $sequenceId
151
     * @return string[]
152
     */
153
    public function analyzeSequenceId($sequenceId)
154
    {
155
        if (!is_string($sequenceId)) {
156
            return array('SequenceID is not a string');
157
        }
158
        $len = strlen($sequenceId);
159
        if ($len < 6 || $len > 40) {
160
            return array(sprintf(
161
                'Invalid SequenceID length. It must be in range [6, 40], but %d received.',
162
                $len
163
            ));
164
        }
165
166
        return array();
167
    }
168
169
    /**
170
     * Analyzes identities from envelope
171
     *
172
     * @param IdentityNodeInterface[] $identities
173
     * @return string
174
     */
175
    public function analyzeIdentities(array $identities)
176
    {
177
        $detail = array();
178
        if (count($identities) === 0) {
179
            $detail[] = 'At least one Identity must be supplied';
180
        } else {
181
            foreach ($identities as $i => $identity) {
182
                if (!$identity instanceof IdentityNodeInterface) {
183
                    $detail[] = $i . '-th elements of Identities not implements IdentityNodeInterface';
184
                }
185
            }
186
        }
187
188
        return $detail;
189
    }
190
191
    /**
192
     * Analyzes envelope type and mandatory fields
193
     *
194
     * @param EnvelopeInterface $envelope
195
     * @return string[]
196
     */
197
    public function analyzeTypeAndMandatoryFields(EnvelopeInterface $envelope)
198
    {
199
        $type = $envelope->getType();
200
        if (!is_string($type)) {
201
            return array('Envelope type must be string');
202
        } elseif (!isset(self::$types[$type])) {
203
            return array(
204
                sprintf('Envelope type "%s" not supported by this client version', $type)
205
            );
206
        } else {
207
            $details = array();
208
            $typeInfo = self::$types[$type];
209
210
            // Mandatory fields check
211
            foreach ($typeInfo['mandatory'] as $name) {
212
                if (!isset($envelope[$name]) || empty($envelope[$name])) {
213
                    $details[] = sprintf(
214
                        'Field "%s" is mandatory for "%s", but not provided',
215
                        $name,
216
                        $type
217
                    );
218
                }
219
            }
220
221
            // Field presence check
222
            $fields = array_merge($typeInfo['mandatory'], $typeInfo['optional']);
223
            $customCount = 0;
224
            foreach ($envelope as $key => $value) {
225
                if ($this->isCustom($type)) {
226
                    $customCount++;
227
                }
228
                if (!in_array($key, $fields)) {
229
                    $details[] = sprintf('Field "%s" not found in "%s"', $key, $envelope->getType());
230
                }
231
            }
232
233
            if ($customCount > 0) {
234
                $details[] = sprintf('Expected 10 or less custom fields, but %d provided', $customCount);
235
            }
236
237
            return $details;
238
        }
239
    }
240
241
    /**
242
     * Analyzes field types
243
     *
244
     * @param EnvelopeInterface $envelope
245
     * @return array
246
     */
247
    public function analyzeFieldTypes(EnvelopeInterface $envelope)
248
    {
249
        $type = $envelope->getType();
250
        if (is_string($type) && isset(self::$types[$type])) {
251
            $details = array();
252
253
            // Per field check
254
            foreach ($envelope as $key => $value) {
255
                // Is custom?
256
                if ($this->isCustom($key)) {
257
                    if (!is_string($value)) {
258
                        $details[] = sprintf(
259
                            'All custom values must be string, but for "%s" %s was provided',
260
                            $key,
261
                            $value === null ? 'null' : gettype($value)
262
                        );
263
                    }
264
                } elseif (isset(self::$dataTypes[$key])) {
265
                    // Checking type
266
                    switch (self::$dataTypes[$key]) {
267
                        case 'string':
268
                            if (!is_string($value)) {
269
                                $details[] = sprintf(
270
                                    'Field "%s" must be string, but %s provided',
271
                                    $key,
272
                                    $value === null ? 'null' : gettype($value)
273
                                );
274
                            } elseif (strlen($value) > 255) {
275
                                $details[] = sprintf(
276
                                    'Received %d bytes to string key "%s" - value is too long',
277
                                    strlen($value),
278
                                    $key
279
                                );
280
                            }
281
                            break;
282 View Code Duplication
                        case 'int':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
283
                            if (!is_int($value)) {
284
                                $details[] = sprintf(
285
                                    'Field "%s" must be int, but %s provided',
286
                                    $key,
287
                                    $value === null ? 'null' : gettype($value)
288
                                );
289
                            }
290
                            break;
291 View Code Duplication
                        case 'float':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
292
                            if (!is_float($value) && !is_int($value)) {
293
                                $details[] = sprintf(
294
                                    'Field "%s" must be float/double, but %s provided',
295
                                    $key,
296
                                    $value === null ? 'null' : gettype($value)
297
                                );
298
                            }
299
                            break;
300 View Code Duplication
                        case 'bool':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
301
                            if (!is_bool($value)) {
302
                                $details[] = sprintf(
303
                                    'Field "%s" must be boolean, but %s provided',
304
                                    $key,
305
                                    $value === null ? 'null' : gettype($value)
306
                                );
307
                            }
308
                            break;
309
                        default:
310
                            $details[] = sprintf('Unknown type for "%s"', $key);
311
                    }
312
                } else {
313
                    $details[] = sprintf('Unknown type for "%s"', $key);
314
                }
315
            }
316
317
            return $details;
318
        }
319
320
        return array();
321
    }
322
323
    /**
324
     * Checks envelope validity and throws an exception on error
325
     *
326
     * @param EnvelopeInterface $envelope
327
     * @throws EnvelopeValidationException
328
     */
329
    public function validate(EnvelopeInterface $envelope)
330
    {
331
        $details = array_merge(
332
            $this->analyzeSequenceId($envelope->getSequenceId()),
333
            $this->analyzeIdentities($envelope->getIdentities()),
334
            $this->analyzeTypeAndMandatoryFields($envelope),
335
            $this->analyzeFieldTypes($envelope)
336
        );
337
338
        if (count($details) > 0) {
339
            throw new EnvelopeValidationException($details);
340
        }
341
    }
342
343
    /**
344
     * Returns true if provided key belongs to custom fields family
345
     *
346
     * @param string $key
347
     * @return bool
348
     */
349
    public function isCustom($key)
350
    {
351
        return is_string($key) && strlen($key) >= 7 && substr($key, 0, 7) === 'custom_';
352
    }
353
}
354