Completed
Push — master ( c1e227...3ff771 )
by Dmitry
03:04
created

Client::reportBad()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
cc 4
nc 2
nop 1
1
<?php
2
3
namespace Rucaptcha;
4
5
use GuzzleHttp\Exception\GuzzleException;
6
use GuzzleHttp\RequestOptions;
7
use Rucaptcha\Exception\ErrorResponseException;
8
use Rucaptcha\Exception\InvalidArgumentException;
9
use Rucaptcha\Exception\RuntimeException;
10
use SimpleXMLElement;
11
12
/**
13
 * Class Client
14
 *
15
 * @package Rucaptcha
16
 * @author Dmitry Gladyshev <[email protected]>
17
 */
18
class Client extends GenericClient
19
{
20
    /* json status codes */
21
    const STATUS_CODE_CAPCHA_NOT_READY = 0;
22
    const STATUS_CODE_OK = 1;
23
24
    /* status codes */
25
    const STATUS_OK_REPORT_RECORDED = 'OK_REPORT_RECORDED';
26
27
    /**
28
     * @var int
29
     */
30
    protected $recaptchaRTimeout = 15;
31
32
    /**
33
     * @var string
34
     */
35
    protected $serverBaseUri = 'http://rucaptcha.com';
36
37
    /**
38
     * Your application ID in Rucaptcha catalog.
39
     * The value `1013` is ID of this library. Set in false if you want to turn off sending any ID.
40
     *
41
     * @see https://rucaptcha.com/software/view/php-api-client
42
     * @var string
43
     */
44
    protected $softId = '1013';
45
46
    /**
47
     * @inheritdoc
48
     */
49
    public function sendCaptcha($content, array $extra = [])
50
    {
51
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
52
            $extra[Extra::SOFT_ID] = $this->softId;
53
        }
54
        return parent::sendCaptcha($content, $extra);
55
    }
56
57
    /**
58
     * Bulk captcha result.
59
     *
60
     * @param int[] $captchaIds # Captcha task Ids array
61
     * @return string[]                 # Array $captchaId => $captchaText or false if is not ready
62
     * @throws GuzzleException
63
     */
64
    public function getCaptchaResultBulk(array $captchaIds)
65
    {
66
        $response = $this->getHttpClient()->request('GET', '/res.php?' . http_build_query([
67
                'key' => $this->apiKey,
68
                'action' => 'get',
69
                'ids' => join(',', $captchaIds)
70
            ]));
71
72
        $captchaTexts = $response->getBody()->__toString();
73
74
        $this->getLogger()->info("Got bulk response: `{$captchaTexts}`.");
75
76
        $captchaTexts = explode("|", $captchaTexts);
77
78
        $result = [];
79
80
        foreach ($captchaTexts as $index => $captchaText) {
81
            $captchaText = html_entity_decode(trim($captchaText));
82
            $result[$captchaIds[$index]] =
83
                ($captchaText == self::STATUS_CAPTCHA_NOT_READY) ? false : $captchaText;
84
        }
85
86
        return $result;
87
    }
88
89
    /**
90
     * Returns balance of account.
91
     *
92
     * @return string
93
     * @throws GuzzleException
94
     */
95
    public function getBalance()
96
    {
97
        $response = $this
98
            ->getHttpClient()
99
            ->request('GET', "/res.php?key={$this->apiKey}&action=getbalance");
100
101
        return $response->getBody()->__toString();
102
    }
103
104
    /**
105
     * Alias of $this->reportBad();
106
     *
107
     * @param string $captchaId
108
     * @return bool
109
     * @throws ErrorResponseException
110
     * @throws GuzzleException
111
     */
112
    public function badCaptcha($captchaId)
113
    {
114
        return $this->reportBad($captchaId);
115
    }
116
117
    /**
118
     * Alias of $this->reportGood();
119
     *
120
     * @param string $captchaId
121
     * @return bool
122
     * @throws ErrorResponseException
123
     * @throws GuzzleException
124
     */
125
    public function goodCaptcha($captchaId)
126
    {
127
        return $this->reportGood($captchaId);
128
    }
129
130
    /**
131
     * Report of wrong recognition.
132
     *
133
     * @param string $captchaId
134
     * @return bool
135
     * @throws ErrorResponseException
136
     * @throws GuzzleException
137
     */
138
    public function reportBad($captchaId)
139
    {
140
        $response = $this
141
            ->getHttpClient()
142
            ->request('GET', "/res.php?key={$this->apiKey}&action=reportbad&id={$captchaId}");
143
144
        $responseText = $response->getBody()->__toString();
145
146
        if ($responseText === self::STATUS_OK_REPORT_RECORDED) {
147
            return true;
148
        }
149
150
        throw new ErrorResponseException(
151
            $this->getErrorMessage($responseText) ?: $responseText,
152
            $this->getErrorCode($responseText) ?: 0
153
        );
154
    }
155
156
157
    /**
158
     * Reports rucaptcha for good recognition.
159
     *
160
     * @param $captchaId
161
     * @return bool
162
     * @throws ErrorResponseException
163
     * @throws GuzzleException
164
     */
165
    public function reportGood($captchaId)
166
    {
167
        $response = $this
168
            ->getHttpClient()
169
            ->request('GET', "/res.php?key={$this->apiKey}&action=reportgood&id={$captchaId}");
170
171
        $responseText = $response->getBody()->__toString();
172
173
        if ($responseText === self::STATUS_OK_REPORT_RECORDED) {
174
            return true;
175
        }
176
177
        throw new ErrorResponseException(
178
            $this->getErrorMessage($responseText) ?: $responseText,
179
            $this->getErrorCode($responseText) ?: 0
180
        );
181
    }
182
183
    /**
184
     * Returns server health data.
185
     *
186
     * @param string|string[] $paramsList   # List of metrics to be returned
187
     * @return string[]|string              # Array of load metrics $metric => $value formatted
188
     * @throws GuzzleException
189
     */
190
    public function getLoad($paramsList = ['waiting', 'load', 'minbid', 'averageRecognitionTime'])
191
    {
192
        $parser = $this->getLoadXml();
193
194
        if (is_string($paramsList)) {
195
            return $parser->$paramsList->__toString();
196
        }
197
198
        $statusData = [];
199
200
        foreach ($paramsList as $item) {
201
            $statusData[$item] = $parser->$item->__toString();
202
        }
203
204
        return $statusData;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $statusData; (array) is incompatible with the return type documented by Rucaptcha\Client::getLoad of type string[]|string.

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...
205
    }
206
207
    /**
208
     * Returns load data as XML.
209
     *
210
     * @return SimpleXMLElement
211
     * @throws GuzzleException
212
     */
213
    public function getLoadXml()
214
    {
215
        $response = $this
216
            ->getHttpClient()
217
            ->request('GET', "/load.php");
218
219
        return new SimpleXMLElement($response->getBody()->__toString());
220
    }
221
222
    /**
223
     * @param string $captchaId     # Captcha task ID
224
     * @return array | false        # Solved captcha and cost array or false if captcha is not ready
225
     * @throws ErrorResponseException
226
     * @throws GuzzleException
227
     */
228
    public function getCaptchaResultWithCost($captchaId)
229
    {
230
        $response = $this
231
            ->getHttpClient()
232
            ->request('GET', "/res.php?key={$this->apiKey}&action=get2&id={$captchaId}");
233
234
        $responseText = $response->getBody()->__toString();
235
236
        if ($responseText === self::STATUS_CAPTCHA_NOT_READY) {
237
            return false;
238
        }
239
240
        if (strpos($responseText, 'OK|') !== false) {
241
            $this->getLogger()->info("Got OK response: `{$responseText}`.");
242
            $data = explode('|', $responseText);
243
            return [
244
                'captcha' => html_entity_decode(trim($data[1])),
245
                'cost' => html_entity_decode(trim($data[2])),
246
            ];
247
        }
248
249
        throw new ErrorResponseException(
250
            $this->getErrorMessage($responseText) ?: $responseText,
251
            $this->getErrorCode($responseText) ?: 0
252
        );
253
    }
254
255
    /**
256
     * Add pingback url to rucaptcha whitelist.
257
     *
258
     * @param string $url
259
     * @return bool                     # true if added and exception if fail
260
     * @throws ErrorResponseException
261
     * @throws GuzzleException
262
     */
263
    public function addPingback($url)
264
    {
265
        $response = $this
266
            ->getHttpClient()
267
            ->request('GET', "/res.php?key={$this->apiKey}&action=add_pingback&addr={$url}");
268
269
        $responseText = $response->getBody()->__toString();
270
271
        if ($responseText === self::STATUS_OK) {
272
            return true;
273
        }
274
275
        throw new ErrorResponseException(
276
            $this->getErrorMessage($responseText) ?: $responseText,
277
            $this->getErrorCode($responseText) ?: 0
278
        );
279
    }
280
281
    /**
282
     * Returns pingback whitelist items.
283
     *
284
     * @return string[]                 # List of urls
285
     * @throws ErrorResponseException
286
     * @throws GuzzleException
287
     */
288
    public function getPingbacks()
289
    {
290
        $response = $this
291
            ->getHttpClient()
292
            ->request('GET', "/res.php?key={$this->apiKey}&action=get_pingback");
293
294
        $responseText = $response->getBody()->__toString();
295
296
        if (strpos($responseText, 'OK|') !== false) {
297
            $data = explode('|', $responseText);
298
            unset($data[0]);
299
            return empty($data[1]) ? [] : array_values($data);
300
        }
301
302
        throw new ErrorResponseException(
303
            $this->getErrorMessage($responseText) ?: $responseText,
304
            $this->getErrorCode($responseText) ?: 0
305
        );
306
    }
307
308
    /**
309
     * Remove pingback url from whitelist.
310
     *
311
     * @param string $uri
312
     * @return bool
313
     * @throws ErrorResponseException
314
     * @throws GuzzleException
315
     */
316
    public function deletePingback($uri)
317
    {
318
        $response = $this
319
            ->getHttpClient()
320
            ->request('GET', "/res.php?key={$this->apiKey}&action=del_pingback&addr={$uri}");
321
322
        $responseText = $response->getBody()->__toString();
323
324
        if ($responseText === self::STATUS_OK) {
325
            return true;
326
        }
327
        throw new ErrorResponseException(
328
            $this->getErrorMessage($responseText) ?: $responseText,
329
            $this->getErrorCode($responseText) ?: 0
330
        );
331
    }
332
333
    /**
334
     * Truncate pingback whitelist.
335
     *
336
     * @return bool
337
     * @throws ErrorResponseException
338
     * @throws GuzzleException
339
     */
340
    public function deleteAllPingbacks()
341
    {
342
        return $this->deletePingback('all');
343
    }
344
345
    /* Recaptcha v2 */
346
347
    /**
348
     * Sent recaptcha v2
349
     *
350
     * @param string $googleKey
351
     * @param string $pageUrl
352
     * @param array $extra
353
     * @return string
354
     * @throws ErrorResponseException
355
     * @throws GuzzleException
356
     */
357
    public function sendRecaptchaV2($googleKey, $pageUrl, $extra = [])
358
    {
359
        $this->getLogger()->info("Try send google key (recaptcha)  on {$this->serverBaseUri}/in.php");
360
361
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
362
            $extra[Extra::SOFT_ID] = $this->softId;
363
        }
364
365
        $response = $this->getHttpClient()->request('POST', "/in.php", [
366
            RequestOptions::QUERY => array_merge($extra, [
367
                'method' => 'userrecaptcha',
368
                'key' => $this->apiKey,
369
                'googlekey' => $googleKey,
370
                'pageurl' => $pageUrl
371
            ])
372
        ]);
373
374
        $responseText = $response->getBody()->__toString();
375
376
        if (strpos($responseText, 'OK|') !== false) {
377
            $this->lastCaptchaId = explode("|", $responseText)[1];
378
            $this->getLogger()->info("Sending success. Got captcha id `{$this->lastCaptchaId}`.");
379
            return $this->lastCaptchaId;
380
        }
381
382
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: "Unknown error: `{$responseText}`.");
383
    }
384
385
    /**
386
     * Alias for bc
387
     * @param $googleKey
388
     * @param $pageUrl
389
     * @param array $extra
390
     * @return string
391
     * @throws ErrorResponseException
392
     * @throws GuzzleException
393
     * @deprecated
394
     */
395
    public function sendRecapthaV2($googleKey, $pageUrl, $extra = [])
396
    {
397
        return $this->sendRecaptchaV2($googleKey, $pageUrl, $extra);
398
    }
399
400
    /**
401
     * Recaptcha V2 recognition.
402
     *
403
     * @param string $googleKey
404
     * @param string $pageUrl
405
     * @param array $extra              # Captcha options
406
     * @return string                   # Code to place in hidden form
407
     * @throws ErrorResponseException
408
     * @throws InvalidArgumentException
409
     * @throws RuntimeException
410
     * @throws GuzzleException
411
     */
412
    public function recognizeRecaptchaV2($googleKey, $pageUrl, $extra = [])
413
    {
414
        $captchaId = $this->sendRecaptchaV2($googleKey, $pageUrl, $extra);
415
        $startTime = time();
416
417
        while (true) {
418
            $this->getLogger()->info("Waiting {$this->rTimeout} sec.");
419
420
            sleep($this->recaptchaRTimeout);
421
422
            if (time() - $startTime >= $this->mTimeout) {
423
                throw new RuntimeException("Captcha waiting timeout.");
424
            }
425
426
            $result = $this->getCaptchaResult($captchaId);
427
428
            if ($result === false) {
429
                continue;
430
            }
431
432
            $this->getLogger()->info("Elapsed " . (time()-$startTime) . " second(s).");
433
434
            return $result;
435
        }
436
437
        throw new RuntimeException('Unknown recognition logic error.');
438
    }
439
440
    /* Recaptcha v3 */
441
442
    /**
443
     * @param string $googleKey
444
     * @param string $pageUrl
445
     * @param string $action
446
     * @param string $minScore
447
     * @param array $extra
448
     * @return string
449
     * @throws ErrorResponseException
450
     * @throws GuzzleException
451
     * @see https://rucaptcha.com/blog/for_webmaster/recaptcha-v3-obhod
452
     */
453
    public function sendRecaptchaV3($googleKey, $pageUrl, $action, $minScore = '0.3', $extra = [])
454
    {
455
        $this->getLogger()->info("Try send google key (recaptcha v3)  on {$this->serverBaseUri}/in.php");
456
457
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
458
            $extra[Extra::SOFT_ID] = $this->softId;
459
        }
460
461
        $response = $this->getHttpClient()->request('POST', "/in.php", [
462
            RequestOptions::QUERY => array_merge($extra, [
463
                'method' => 'userrecaptcha',
464
                'version' => 'v3',
465
                'key' => $this->apiKey,
466
                'googlekey' => $googleKey,
467
                'pageurl' => $pageUrl,
468
                'action' => $action,
469
                'min_score' => $minScore
470
            ])
471
        ]);
472
473
        $responseText = $response->getBody()->__toString();
474
475
        if (strpos($responseText, 'OK|') !== false) {
476
            $this->lastCaptchaId = explode("|", $responseText)[1];
477
            $this->getLogger()->info("Sending success. Got captcha id `{$this->lastCaptchaId}`.");
478
            return $this->lastCaptchaId;
479
        }
480
481
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: "Unknown error: `{$responseText}`.");
482
    }
483
484
    /**
485
     * @param string $googleKey
486
     * @param string $pageUrl
487
     * @param string $action
488
     * @param string $minScore
489
     * @param array $extra
490
     * @return false|string
491
     * @throws ErrorResponseException
492
     * @throws InvalidArgumentException
493
     * @throws RuntimeException
494
     * @throws GuzzleException
495
     */
496
    public function recognizeRecaptchaV3($googleKey, $pageUrl, $action, $minScore = '0.3', $extra = [])
497
    {
498
        $captchaId = $this->sendRecaptchaV3($googleKey, $pageUrl, $action, $minScore, $extra);
499
        $startTime = time();
500
501
        while (true) {
502
            $this->getLogger()->info("Waiting {$this->rTimeout} sec.");
503
504
            sleep($this->recaptchaRTimeout);
505
506
            if (time() - $startTime >= $this->mTimeout) {
507
                throw new RuntimeException("Captcha waiting timeout.");
508
            }
509
510
            $result = $this->getCaptchaResult($captchaId);
511
512
            if ($result === false) {
513
                continue;
514
            }
515
516
            $this->getLogger()->info("Elapsed " . (time()-$startTime) . " second(s).");
517
518
            return $result;
519
        }
520
521
        throw new RuntimeException('Unknown recognition logic error.');
522
    }
523
524
    /**
525
     * Keycaptcha recognition.
526
     *
527
     * @param string $SSCUserId
528
     * @param string $SSCSessionId
529
     * @param string $SSCWebServerSign
530
     * @param string $SSCWebServerSign2
531
     * @param string $pageUrl
532
     * @param array $extra
533
     * @return string                       # Captcha ID
534
     * @throws ErrorResponseException
535
     * @throws GuzzleException
536
     */
537
    public function sendKeyCaptcha(
538
        $SSCUserId,
539
        $SSCSessionId,
540
        $SSCWebServerSign,
541
        $SSCWebServerSign2,
542
        $pageUrl,
543
        $extra = []
544
    ) {
545
    
546
        $this->getLogger()->info("Try send google key (recaptcha)  on {$this->serverBaseUri}/in.php");
547
548
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
549
            $extra[Extra::SOFT_ID] = $this->softId;
550
        }
551
552
        $response = $this->getHttpClient()->request('POST', "/in.php", [
553
            RequestOptions::QUERY => array_merge($extra, [
554
                'method' => 'keycaptcha',
555
                'key' => $this->apiKey,
556
                's_s_c_user_id' => $SSCUserId,
557
                's_s_c_session_id' => $SSCSessionId,
558
                's_s_c_web_server_sign' => $SSCWebServerSign,
559
                's_s_c_web_server_sign2' => $SSCWebServerSign2,
560
                'pageurl' => $pageUrl
561
            ])
562
        ]);
563
564
        $responseText = $response->getBody()->__toString();
565
566
        if (strpos($responseText, 'OK|') !== false) {
567
            $this->lastCaptchaId = explode("|", $responseText)[1];
568
            $this->getLogger()->info("Sending success. Got captcha id `{$this->lastCaptchaId}`.");
569
            return $this->lastCaptchaId;
570
        }
571
572
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: "Unknown error: `{$responseText}`.");
573
    }
574
575
    /**
576
     * Keycaptcha recognition.
577
     *
578
     * @param string $SSCUserId
579
     * @param string $SSCSessionId
580
     * @param string $SSCWebServerSign
581
     * @param string $SSCWebServerSign2
582
     * @param string $pageUrl
583
     * @param array $extra
584
     * @return string                       # Code to place into id="capcode" input value
585
     * @throws ErrorResponseException
586
     * @throws InvalidArgumentException
587
     * @throws RuntimeException
588
     * @throws GuzzleException
589
     */
590
    public function recognizeKeyCaptcha(
591
        $SSCUserId,
592
        $SSCSessionId,
593
        $SSCWebServerSign,
594
        $SSCWebServerSign2,
595
        $pageUrl,
596
        $extra = []
597
    ) {
598
        $captchaId = $this
599
            ->sendKeyCaptcha($SSCUserId, $SSCSessionId, $SSCWebServerSign, $SSCWebServerSign2, $pageUrl, $extra);
600
601
        $startTime = time();
602
603
        while (true) {
604
            $this->getLogger()->info("Waiting {$this->rTimeout} sec.");
605
606
            sleep($this->recaptchaRTimeout);
607
608
            if (time() - $startTime >= $this->mTimeout) {
609
                throw new RuntimeException("Captcha waiting timeout.");
610
            }
611
612
            $result = $this->getCaptchaResult($captchaId);
613
614
            if ($result === false) {
615
                continue;
616
            }
617
618
            $this->getLogger()->info("Elapsed " . (time()-$startTime) . " second(s).");
619
620
            return $result;
621
        }
622
623
        throw new RuntimeException('Unknown recognition logic error.');
624
    }
625
626
    /**
627
     * Override generic method for using json response.
628
     *
629
     * @param string $captchaId # Captcha task ID
630
     * @return false|string             # Solved captcha text or false if captcha is not ready
631
     * @throws ErrorResponseException
632
     * @throws InvalidArgumentException
633
     * @throws GuzzleException
634
     */
635
    public function getCaptchaResult($captchaId)
636
    {
637
        $response = $this
638
            ->getHttpClient()
639
            ->request('GET', "/res.php?key={$this->apiKey}&action=get&id={$captchaId}&json=1");
640
641
        $responseData = json_decode($response->getBody()->__toString(), true);
642
643
        if (JSON_ERROR_NONE !== json_last_error()) {
644
            throw new InvalidArgumentException(
645
                'json_decode error: ' . json_last_error_msg()
646
            );
647
        }
648
649
        if ($responseData['status'] === self::STATUS_CODE_CAPCHA_NOT_READY) {
650
            return false;
651
        }
652
653
        if ($responseData['status'] === self::STATUS_CODE_OK) {
654
            $this->getLogger()->info("Got OK response: `{$responseData['request']}`.");
655
            return $responseData['request'];
656
        }
657
658
        throw new ErrorResponseException(
659
            $this->getErrorMessage(
660
                $responseData['request']
661
            ) ?: $responseData['request'],
662
            $responseData['status']
663
        );
664
    }
665
666
    /**
667
     * Match error code by response.
668
     *
669
     * @param string $responseText
670
     * @return int
671
     */
672
    private function getErrorCode($responseText)
673
    {
674
        if (preg_match('/ERROR:\s*(\d{0,4})/ui', $responseText, $matches)) {
675
            return intval($matches[1]);
676
        }
677
        return 0;
678
    }
679
}
680