KeyPic   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 418
Duplicated Lines 2.63 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 7
Bugs 1 Features 1
Metric Value
wmc 34
c 7
b 1
f 1
lcom 2
cbo 2
dl 11
loc 418
rs 9.2

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A setHost() 0 6 1
A setVersion() 0 6 1
A setUserAgent() 0 6 1
A setFormID() 0 6 1
A setPublisherID() 0 6 1
A setDebug() 0 6 1
A getTokenInputName() 0 4 1
A setTokenInputName() 0 6 1
A checkFormID() 0 13 2
B getToken() 6 33 4
A getTokenInput() 0 7 2
B getIt() 0 30 4
A renderHtml() 0 4 1
B isSpam() 5 29 5
B reportSpam() 0 22 4
A sendRequest() 0 20 2
B getWebServiceFields() 0 31 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Zrashwani\KeyPic;
4
5
use Psr\Http\Message\ServerRequestInterface;
6
7
/**
8
 * KeyPic
9
 *
10
 * This class will provide a wrapper on keypic webservice for anti-spam
11
 * compliant with PSR-7 requests
12
 *
13
 * @package Keypic
14
 * @author  Zeid Rashwani <http://zrashwani.com>
15
 * @version 0.0.1
16
 */
17
18
class KeyPic
19
{
20
21
    /*@var RequestInterface $request */
22
    private $request;
23
        
24
    /**
25
     * keypic API version number
26
     * @var string
27
     */
28
    private $version;
29
    
30
    /**
31
     * keypic API user agent text
32
     * @var string
33
     */
34
    private $UserAgent;
35
    
36
    /**
37
     * keypic webservice host
38
     * @var string
39
     */
40
    private $host;
41
    
42
    /**
43
     * keypic formID, unique for each client
44
     * @var string
45
     */
46
    private $FormID;
47
    
48
    /**
49
     * keypic publisher ID
50
     * @var string
51
     */
52
    private $PublisherID;
53
    
54
    /**
55
     * Token generated from keypic API
56
     * @var string
57
     */
58
    private $Token;
59
    
60
    /**
61
     * debug flag
62
     * @var boolean
63
     */
64
    private $Debug;
65
    
66
    /**
67
     * name of token field hidden input
68
     * @var string
69
     */
70
    private $TokenInputName;
71
72
73
    /**
74
     * constructor with injecting psr-7 request in the object
75
     * @param ServerRequestInterface $request
76
     */
77
    public function __construct(ServerRequestInterface $request, $vesion = '2.1')
78
    {
79
        $this->request   = $request;
0 ignored issues
show
Documentation Bug introduced by
It seems like $request of type object<Psr\Http\Message\ServerRequestInterface> is incompatible with the declared type object<Zrashwani\KeyPic\RequestInterface> of property $request.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
80
        $this->version   = $vesion;
81
        $this->UserAgent = 'Keypic PHP Class, Version: '.$this->version;
82
        $this->host      = 'ws.keypic.com';
83
    }
84
85
    /**
86
     * set API host value
87
     * @param string $host
88
     * @return \Zrashwani\KeyPic\KeyPic
89
     */
90
    public function setHost($host)
91
    {
92
        $new = clone $this;
93
        $new->host = $host;
94
        return $new;
95
    }
96
97
    /**
98
     * set keypic api version
99
     * @param string $version
100
     * @return \Zrashwani\KeyPic\KeyPic
101
     */
102
    public function setVersion($version)
103
    {
104
        $new = clone $this;
105
        $new->version = $version;
106
        return $new;
107
    }
108
109
    /**
110
     * set user agent
111
     * @param string $UserAgent
112
     * @return \Zrashwani\KeyPic\KeyPic
113
     */
114
    public function setUserAgent($UserAgent)
115
    {
116
        $new = clone $this;
117
        $new->UserAgent = $UserAgent;
118
        return $new;
119
    }
120
121
    /**
122
     * set keypic formId value
123
     * @param string $FormID
124
     * @return \Zrashwani\KeyPic\KeyPic
125
     */
126
    public function setFormID($FormID)
127
    {
128
        $new = clone $this;
129
        $new->FormID = $FormID;
130
        return $new;
131
    }
132
133
    /**
134
     * set publisher Id value
135
     * @param string $PublisherID
136
     * @return \Zrashwani\KeyPic\KeyPic
137
     */
138
    public function setPublisherID($PublisherID)
139
    {
140
        $new = clone $this;
141
        $new->PublisherID = $PublisherID;
142
        return $new;
143
    }
144
145
    /**
146
     * set Debug value
147
     * @param boolean $Debug
148
     * @return \Zrashwani\KeyPic\KeyPic
149
     */
150
    public function setDebug($Debug)
151
    {
152
        $new = clone $this;
153
        $new->Debug = $Debug;
154
        return $new;
155
    }
156
    
157
    /**
158
     * get token hidden input name
159
     * @return string
160
     */
161
    public function getTokenInputName()
162
    {
163
        return $this->TokenInputName;
164
    }
165
    
166
    /**
167
     * set token input hidden field name
168
     * @param string $tokenInputName
169
     * @return \Zrashwani\KeyPic\KeyPic
170
     */
171
    public function setTokenInputName($tokenInputName)
172
    {
173
        $new = clone $this;
174
        $new->TokenInputName = $tokenInputName;
175
        return $new;
176
    }
177
178
    /**
179
     * check keypic formID if valid
180
     * @param String $FormID
181
     * @return boolean
182
     */
183
    public function checkFormID($FormID)
184
    {
185
        $fields['RequestType'] = 'checkFormID';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$fields was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fields = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
186
        $fields['ResponseType'] = '2';
187
        $fields['FormID'] = $FormID;
188
189
        $response = $this->sendRequest($fields);
190
        if (empty($response) !== false) {
191
            return $response;
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * requesting new token or getting its value if it is already generated
199
     * @param String $Token
200
     * @param String $ClientEmailAddress
201
     * @param String $ClientUsername
202
     * @param String $ClientMessage
203
     * @param String $ClientFingerprint
204
     * @param int $Quantity
205
     * @return boolean
206
     */
207
    public function getToken(
208
        $Token = '',
209
        $ClientEmailAddress = '',
210
        $ClientUsername = '',
211
        $ClientMessage = '',
212
        $ClientFingerprint = '',
213
        $Quantity = 1
214
    ) {
215
        if ($Token) {
216
            $this->Token = $Token;
217
            return $this->Token;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->Token; (string) is incompatible with the return type documented by Zrashwani\KeyPic\KeyPic::getToken of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
218
        } else {
219
            $fields = $this->getWebServiceFields(
220
                "RequestNewToken",
221
                $ClientEmailAddress,
222
                $ClientUsername,
223
                $ClientFingerprint,
224
                $ClientMessage,
225
                $Quantity
226
            );
227
            $response = $this->sendRequest($fields);
228
229 View Code Duplication
            if ($response['status'] == 'new_token') {
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...
230
                $this->Token = $response['Token'];
231
                return $response['Token'];
232
            } elseif ($response['status'] == 'error') {
233
                throw new \Exception("Keypic error generation, ".$response['error']);
234
            }
235
        }
236
237
        $this->Token = false;
0 ignored issues
show
Documentation Bug introduced by
The property $Token was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
238
        return false;
239
    }
240
241
    /**
242
     * get keypic token hidden field text
243
     * @return boolean|string
244
     */
245
    public function getTokenInput()
246
    {
247
        if ($this->Token) {
248
            return '<input type="hidden" name="'.$this->TokenInputName.'" value="' . $this->Token . '" />';
249
        }
250
        return false;
251
    }
252
253
    /**
254
     * print keypic token into form as pixel image or script
255
     * @param string $RequestType
256
     * @param string $WidthHeight
257
     * @return string
258
     */
259
    public function getIt($RequestType = 'getScript', $WidthHeight = '336x280')
260
    {
261
        $ret = "";
262
        if ($this->Token) {
263
            switch ($RequestType) {
264
                case 'getImage':
265
                    $ret =  '<a href="http://' . $this->host . '/?RequestType=getClick&amp;Token=' .
266
                        $this->Token . '" target="_blank"><img src="//' . $this->host
267
                        . '/?RequestType=getImage&amp;Token=' . $this->Token
268
                        . '&amp;WidthHeight=' . $WidthHeight . '&amp;Debug='.$this->Debug
269
                        . '&amp;PublisherID=' . $this->PublisherID
270
                        . '" alt="Form protected by Keypic" /></a>';
271
                    break;
272
273
                default:
274
                    $ret =  '<script type="text/javascript" src="//' . $this->host .
275
                        '/?RequestType=getScript&amp;Token=' . $this->Token
276
                        . '&amp;WidthHeight=' . $WidthHeight . '&amp;Debug='.$this->Debug
277
                        . '&amp;PublisherID=' . $this->PublisherID . '"></script>';
278
                    break;
279
            }
280
        }
281
282
        if (empty($ret) === true) {
283
            $ret = '<a href="http://keypic.com" target="_blank">'.
284
                    'Incorrect keypic configuration</a>';
285
        }
286
        
287
        return $ret;
288
    }
289
    
290
    /**
291
     * render necessary keypic input and script to use in html form
292
     * @param string $RequestType
293
     * @param string $WidthHeight
294
     * @return String
295
     */
296
    public function renderHtml($RequestType = 'getScript', $WidthHeight = '336x280')
297
    {
298
        return $this->getTokenInput().$this->getIt($RequestType, $WidthHeight);
299
    }
300
301
    /**
302
     * Detect if entry is Spam? from 0% to 100%
303
     * @param string $ClientEmailAddress
304
     * @param string $ClientUsername
305
     * @param string $ClientMessage
306
     * @param string $ClientFingerprint
307
     * @throw \Exception
308
     * @return array
309
     *
310
     */
311
    public function isSpam(
312
        $ClientEmailAddress = '',
313
        $ClientUsername = '',
314
        $ClientMessage = '',
315
        $ClientFingerprint = ''
316
    ) {
317
        $this->Token = $this->request->getParsedBody()[$this->TokenInputName];
318
319
        if (($this->Token) && ($this->FormID)) {
320
            $fields = $this->getWebServiceFields(
321
                "RequestValidation",
322
                $ClientUsername,
323
                $ClientEmailAddress,
324
                $ClientMessage,
325
                $ClientFingerprint
326
            );
327
            $fields['Token'] = $this->Token;
328
329
            $response = $this->sendRequest($fields);
330
331 View Code Duplication
            if ($response['status'] == 'response') {
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...
332
                return $response['spam'];
333
            } elseif ($response['status'] == 'error') {
334
                throw new \Exception("Error validating keypic token, error: ".$response['error']);
335
            }
336
        } else {
337
            throw new \Exception("Empty keypic token or Form ID");
338
        }
339
    }
340
341
    /**
342
     * sending reportSpam method to keypic webservice
343
     * @return boolean|array
344
     */
345
    public function reportSpam()
346
    {
347
        $Token = $this->request->getParsedBody()[$this->TokenInputName];
348
        if ($Token == '') {
349
            return 'error';
350
        }
351
        if ($this->FormID == '') {
352
            return 'error';
353
        }
354
355
        $fields['Token'] = $Token;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$fields was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fields = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
356
        $fields['FormID'] = $this->FormID;
357
        $fields['RequestType'] = 'ReportSpam';
358
        $fields['ResponseType'] = '2';
359
360
        $response = $this->sendRequest($fields);
361
        if (empty($response) !== true) {
362
            return $response;
363
        } else {
364
            return false;
365
        }
366
    }
367
368
    /**
369
     * sending spam detection request to keypic servers
370
     * @param  array          $fields
371
     * @return boolean|array
372
     */
373
    private function sendRequest(array $fields)
374
    {
375
        $client = new \GuzzleHttp\Client(['timeout'=>3]);
376
        $response = $client->post(
377
            "http://".$this->host,
378
            ['headers' => ['content-type' => 'application/x-www-form-urlencoded',
379
                            'User-Agent'   => $this->UserAgent],
380
              'body'  => $fields,
381
              'version' => 1.0
382
              ]
383
        );
384
        
385
             $result = $response->getBody()->getContents();
386
        
387
        if (empty($result) !== true) {
388
            return json_decode($result, true);
389
        }
390
391
                return false;
392
    }
393
    
394
    /**
395
     * getting fields used in webservice for requesting and validating keypic token
396
     * @param String $RequestType
397
     * @param String $ClientUsername
398
     * @param String $ClientEmailAddress
399
     * @param String $ClientMessage
400
     * @param String $ClientFingerprint
401
     * @param int $Quantity
402
     * @return array
403
     */
404
    protected function getWebServiceFields(
405
        $RequestType,
406
        $ClientUsername = "",
407
        $ClientEmailAddress = "",
408
        $ClientMessage = "",
409
        $ClientFingerprint = "",
410
        $Quantity = 1
411
    ) {
412
        
413
            $serverParams = $this->request->getServerParams();
414
            $fields = [];
415
            
416
            $fields['FormID'] = $this->FormID;
417
            $fields['RequestType'] = $RequestType;
418
            $fields['ResponseType'] = '2';
419
            $fields['Quantity'] = $Quantity;
420
            $fields['ServerName'] = $serverParams['SERVER_NAME'];
421
            $fields['ClientIP'] = $serverParams['REMOTE_ADDR'];
422
            $fields['ClientUserAgent'] = $this->request->getHeader('user-agent')[0];
423
            $fields['ClientAccept'] = $this->request->getHeader('accept')[0];
424
            $fields['ClientAcceptEncoding'] = $this->request->getHeader('accept-encoding')[0];
425
            $fields['ClientAcceptLanguage'] = $this->request->getHeader('accept-language')[0];
426
            $fields['ClientAcceptCharset'] = $this->request->getHeader('accept-charset')[0];
427
            $fields['ClientHttpReferer'] = $this->request->getHeader('referer')[0];
428
            $fields['ClientUsername'] = $ClientUsername;
429
            $fields['ClientEmailAddress'] = $ClientEmailAddress;
430
            $fields['ClientMessage'] = $ClientMessage;
431
            $fields['ClientFingerprint'] = $ClientFingerprint;
432
            
433
            return $fields;
434
    }
435
}
436